Back to all posts

CL_ABAP_PARALLEL: Parallel Processing Without the aRFC Circus

André Schärpf

Parallel processing in ABAP has always meant one thing: asynchronous RFC. CALL FUNCTION STARTING NEW TASK, DESTINATION IN GROUP DEFAULT, a callback FORM, a counter variable, WAIT UNTIL, and RECEIVE RESULTS FROM FUNCTION. The business logic lives in an RFC-enabled function module. The orchestration lives in the calling report. The result handling lives in a callback. Three places for one concern.

It works. The boilerplate-to-logic ratio is brutal.

CL_ABAP_PARALLEL has been around longer than most ABAP developers seem to realize. It does the same job with a fraction of the ceremony.

The old way

The classic aRFC pattern looks something like this:

DATA lv_count TYPE i.

DO 10 TIMES.
  CALL FUNCTION 'Z_DO_WORK'
    STARTING NEW TASK |TASK{ sy-index }|
    DESTINATION IN GROUP DEFAULT
    PERFORMING on_complete ON END OF TASK
    EXPORTING
      p_input = sy-index.
ENDDO.

WAIT UNTIL lv_count >= 10.

FORM on_complete USING lv_task_name TYPE clike.
  DATA ls_result TYPE string.
  RECEIVE RESULTS FROM FUNCTION 'Z_DO_WORK'
    IMPORTING p_result = ls_result.
  lv_count = lv_count + 1.
ENDFORM.

You need an RFC-enabled function module. You need a callback routine. You need a counter. You need to manage task names. And if something fails, you piece together what happened from the callback state.

This pattern is also not available in ABAP Cloud. CALL FUNCTION STARTING NEW TASK is not a released statement there.

The replacement

CL_ABAP_PARALLEL takes a different approach. You define a class that implements IF_ABAP_PARALLEL, put your data as instance attributes, and put your logic in the DO method. The framework handles the rest.

CLASS lcl_square DEFINITION.
  PUBLIC SECTION.
    INTERFACES if_abap_parallel.
    METHODS constructor IMPORTING p_value TYPE i.
    DATA input  TYPE i.
    DATA result TYPE i.
ENDCLASS.

CLASS lcl_square IMPLEMENTATION.
  METHOD constructor.
    input = p_value.
  ENDMETHOD.

  METHOD if_abap_parallel~do.
    result = input * input.
  ENDMETHOD.
ENDCLASS.

The IF_ABAP_PARALLEL interface has one method: DO. It extends IF_SERIALIZABLE_OBJECT, which means the framework can serialize your instances across work processes. Input goes in as attributes before execution, output comes back as attributes after.

One important detail: a plain IF_ABAP_PARALLEL implementation does not need super->constructor( ). If you copied that line from SAP's class header comment example, remove it unless your task class actually inherits from another class.

Running it:

DATA(lo_parallel) = NEW cl_abap_parallel( ).

DATA lt_tasks TYPE cl_abap_parallel=>t_in_inst_tab.
DO 10 TIMES.
  APPEND NEW lcl_square( sy-index ) TO lt_tasks.
ENDDO.

lo_parallel->run_inst(
  EXPORTING p_in_tab  = lt_tasks
  IMPORTING p_out_tab = DATA(lt_results)
).

Build a list of instances. Call run_inst(). Done. No function modules, no callbacks, no task counters. The business logic, the input, and the output all live in one place.

Tuning the constructor

The constructor controls resource usage:

  • p_percentage: maximum percentage of available work processes to use. Defaults to 50. Set this lower on busy systems.
  • p_num_tasks: upper limit on concurrent tasks. Defaults to 20,000.
  • p_timeout: per-task timeout in seconds. Defaults to the current maximum runtime returned by CL_RUNTIME=>GET_MY_MAX_RUNTIME().
  • p_abort_on_error: when set, aborts all remaining tasks if one fails.
  • p_local_server: restricts execution to the current application server.

The run_inst() and run() methods also accept a p_debug flag. When set, the framework processes tasks sequentially instead of in parallel. Essential for stepping through your DO method with the debugger.

Reading results

The output table T_OUT_INST has one entry per task:

LOOP AT lt_results ASSIGNING FIELD-SYMBOL(<r>).
  IF <r>-inst IS INITIAL.
    WRITE: / |Task { <r>-index } failed: { <r>-message }|.
    CONTINUE.
  ENDIF.
  DATA(lo_task) = CAST lcl_square( <r>-inst ).
  WRITE: / |{ lo_task->input } squared = { lo_task->result }|.
ENDLOOP.

Each result carries the processed instance reference (inst), its position in the input list (index), how long the task took in seconds (time), and an error message if the task did not complete (message). When inst is initial, the task failed. The message field tells you why.

The xstring alternative

There is a second pattern using the run() method instead of run_inst(). You inherit from CL_ABAP_PARALLEL, redefine the DO method, and work with XSTRING parameters directly:

METHOD do.
  DATA lv_input TYPE i.
  CALL TRANSFORMATION id SOURCE XML p_in RESULT input = lv_input.

  DATA(lv_output) = lv_input * lv_input.

  CALL TRANSFORMATION id SOURCE output = lv_output RESULT XML p_out.
ENDMETHOD.

This approach requires manual serialization via CALL TRANSFORMATION ID. It is more flexible when tasks have different data shapes, but for most cases run_inst() is simpler and easier to read.

Fork and join

run_inst() blocks until all tasks complete. If you need to do other work while tasks are running, use the fork/join pattern:

lo_parallel->fork_inst( p_in_tab = lt_tasks ).

" ... do other work here ...

lo_parallel->join_inst( IMPORTING p_out_tab = DATA(lt_results) ).

fork_inst() dispatches the tasks and returns immediately. join_inst() blocks until they finish. If you want to poll instead of block, is_completed() returns abap_true when all tasks are done.

Availability

On on-premise systems, CL_ABAP_PARALLEL is older than many people think. The class itself exists on 7.50 systems. IF_ABAP_PARALLEL, which powers the object-based run_inst() pattern, is available from 7.54.

In ABAP Cloud, CL_ABAP_PARALLEL is released. The ABAP Cloud keyword docs for both CALL FUNCTION STARTING NEW TASK and WAIT FOR ASYNCHRONOUS TASKS explicitly say those statements are not part of the current ABAP language version. If you are writing cloud-ready ABAP, CL_ABAP_PARALLEL is the supported way to do parallel processing.