Время от времени появляются задачи, в которых необходимо выполнить операции, занимающие продолжительное время.

Введение

Время от времени появляются задачи, в которых необходимо выполнить операции, занимающие продолжительное время. Если операции не зависят друг от друга, то для уменьшения времени выполнения можно использовать параллельное программирование.

Постановка проблемы

Реализация параллельного программирования в ABAP обычно включает следующие шаги:

  1. Создание RFC функционального модуля (ФМ).
  2. Реализация внутри него бизнес-логики.
  3. Асинхронный вызов RFC ФМ в цикле.
  4. Ожидание выполнения и получение результатов работы.

Если посмотреть на получившийся список, то можно заметить, что по большому счету нас интересуют только шаги 2 и 4. Все остальное — это рутинная работа, которая каждый раз занимает время и потенциально может быть источником ошибок.

Чтобы не создавать RFC ФМ каждый раз, когда необходимо выполнить параллельную обработку, можно использовать SPTA Framework, который нам предоставил вендор.

SPTA Framework — это  хороший инструмент, но интерфейс взаимодействия с ним оставляет желать лучшего. Из-за этого разработчику приходится прикладывать немалые усилия для того, чтобы реализовать сам процесс распараллеливания.

Кроме того, написать чистый код, используя непосредственно SPTA Framework, тоже не самая простая задача. Например, нужно хорошо постараться, чтобы избежать использования глобальных переменных. Но если с этим еще можно справиться, то избежать использования подпрограмм (FORM) не выйдет. В конечном итоге код может получиться запутанным и тяжело поддерживаемым.

Решение

Для решения рассмотренных проблем предлагается использовать ABAP Concurrency API.

ABAP Concurrency API – это несколько классов, предназначенных для реализации параллельных вычислений на базе SPTA Framework.

На рис. 1 представлена UML-диаграмма классов ABAP Concurrency API.

Рис. 1. UML-диаграмма классов ABAP Concurrency API

Использование ABAP Concurrency API позволяет разработчику мыслить более абстрактно. Ему больше не нужно акцентировать внимание на распараллеливании. Вместо этого он может уделить больше времени бизнес-логике своего приложения.

Установка

Проект располагается здесь.

Установка выполняется с помощью abapGit.

Пример использования

Рассмотрим простую задачу.

Необходимо найти квадраты чисел от 1 до 10.

Квадрат каждого из чисел будем искать в отдельном диалоговом процессе (DIA, tcode sm50). Пример оторван от реального мира, но его достаточно для того, чтобы понять, как работать с API.

Для начала создадим 3 класса: Контекст, Задача и Результат.

  1. lcl_contex, объект этого класса будет инкапсулировать параметры задачи. Использование этого класса не обязательно. Можно обойтись и без него, передав параметры задачи непосредственно в ее конструктор. Однако использование отдельного класса, на мой взгляд, предпочтительнее.
CLASS lcl_context DEFINITION FINAL.
  PUBLIC SECTION.
    INTERFACES: if_serializable_object.

 
    TYPES: BEGIN OF ty_params,
             param TYPE i,
           END OF ty_params.

 
    METHODS: constructor IMPORTING is_params TYPE ty_params,
             get RETURNING VALUE(rs_params) TYPE ty_params.

 
  PRIVATE SECTION.
    DATA: ms_params TYPE ty_params.
ENDCLASS.

 
CLASS lcl_context IMPLEMENTATION.
  METHOD constructor.
    ms_params = is_params.
  ENDMETHOD.

 
  METHOD get.
    rs_params = ms_params.
  ENDMETHOD.
ENDCLASS.
  1. lcl_task описывает объект Задача. Содержит бизнес-логику (в нашем случае возведение числа в степень 2). Обратите внимание, что класс lcl_task наследуется от класса zcl_capi_abstract_task и переопределяет метод zif_capi_callable~call.
CLASS lcl_task DEFINITION INHERITING FROM zcl_capi_abstract_task FINAL.

  PUBLIC SECTION.

    METHODS: constructor IMPORTING io_context TYPE REF TO lcl_context,

             zif_capi_callable~call REDEFINITION.

  PRIVATE SECTION.

    DATA: mo_context TYPE REF TO lcl_context.

    DATA: mv_res TYPE i.

ENDCLASS.

CLASS lcl_task IMPLEMENTATION.

  METHOD constructor.

    super->constructor( ).

    mo_context = io_context.

  ENDMETHOD.

  METHOD zif_capi_callable~call.

    DATA(ls_params) = mo_context->get( ).

    mv_res = ls_params-param ** 2.

    ro_result = new lcl_result( iv_param  = ls_params-param

                                iv_result = mv_res ).

  ENDMETHOD.

ENDCLASS.
  1. lcl_result описывает Результат выполнения задачи. Этот класс должен реализовывать интерфейс if_serializable_object. В остальном вы можете описать его произвольным образом.
CLASS lcl_result DEFINITION FINAL.

  PUBLIC SECTION.

    INTERFACES: if_serializable_object.

    METHODS: constructor IMPORTING iv_param  TYPE i

                                   iv_result TYPE i,

             get RETURNING VALUE(rv_result) TYPE string.

  PRIVATE SECTION.

    DATA: mv_param TYPE i.

    DATA: mv_result TYPE i.

ENDCLASS.

CLASS lcl_result IMPLEMENTATION.

  METHOD constructor.

    mv_param = iv_param.

    mv_result = iv_result.

  ENDMETHOD.

  METHOD get.

    rv_result = |{ mv_param } -> { mv_result }|.

  ENDMETHOD.

ENDCLASS.

Внимание: объекты классов lcl_task и lcl_result будут сериализованы/десериализованы в процессе выполнения, поэтому избегайте использования статичных атрибутов. Статичные

Если хотите прочитать статью полностью и оставить свои комментарии присоединяйтесь к sapland

У вас уже есть учетная запись?

Войти