Using class CL_ABAP_PARALLEL is a convenient way to mass process in parallel dialog work processes. This can be especially powerful in a system with more dialog vs other types of work processes. Due to limited documentation, there was a small learning curve when I first implemented this in an S4/HANA on-premise system on SAP_BASIS version 7.54. I’m writing this blog to help others with their implementation of this class.
Setup overview
In this example there will be two local classes (to make it an easy copy/paste test). The XSTRING data type is used to universally pass different kinds of data between the two classes.
- Local class MAIN
- The start and end point for the main logic outside of parallel processing
- Local class SINGLE_TASK
- Inherits from CL_ABAP_PARALLEL
- Contains logic to process one of the parallel task records at a time
Logic overview for class MAIN
- Collect the necessary data for all the tasks
- EXPORT all the task data and any shared task data
- Create an instance of SINGLE_TASK passing parameters to control how many resources are used
- See the Constructor parameters listed below
- Call the RUN Method of SINGLE_TASK passing all the collected data for all tasks and receiving the results for all tasks
- See the RUN Method parameters listed below, including the results/output structure details
- IMPORT the task results to process them
Logic overview for class SINGLE_TASK
- The DO Method is called once per task data record
- IMPORT the single task record and any shared task data
- Process the data as necessary
- EXPORT the task results
Constructor parameters of CL_ABAP_PARALLEL
- P_NUM_TASKS
- If supplied, this will simply override P_NUM_PROCESSES
- P_TIMEOUT
- Amount of seconds the dialog process can run before automatically timing out
- P_PERCENTAGE
- Limits the number of simultaneous parallel processes by calculating the system allowable max parallel tasks across all application servers and reduces it according to the percentage.
- For example, if there are two application servers with each having 100 as their max parallel task count and P_PERCENTAGE = 75; then the internal logic would limit the number of simultaneous parallel processes to 150
- This value is ignored if P_NUM_PROCESSES is less than the % calculation
- P_NUM_PROCESSES
- Limits the number of simultaneous parallel processes
- This is ignored if the number is greater than the allowable maximum parallel tasks across all application servers.
- This value is also ignored whenever P_PERCENTAGE calculates to a lower number
- P_LOCAL_SERVER
- Optionally restrict the simultaneous parallel processes to only the current application server
- This also impacts the internal calculations used for P_PERCENTAGE and P_NUM_PROCESSES
Notes: Class CL_SSI_DISPATCH is primarily controlling the server dispatch logic. Function Module RS_ABAP_PARALLEL is used internally for executing the parallel tasks.
RUN Method parameters of CL_ABAP_PARALLEL
- P_IN_TAB
- Table of XSTRING values used as input for the parallel dialog tasks
- One input row per dialog task
- P_IN_ALL
- Single XSTRING value
- Shared record that’s available in all parallel dialog tasks
- P_DEBUG
- Debugging flag is used to skip the parallel framework and directly/sequentially call the DO Method
- P_OUT_TAB
- Table of XSTRING values
- One output row per dialog task
- Structure fields
- RESULT – XSTRING of results populated within the DO Method
- INDEX – Index of the table in P_IN_TAB
- TIME – Seconds of runtime
- MESSAGE – Error message for timeouts and other failures
Conclusion
Hopefully these details save you some time when implementing class CL_ABAP_PARALLEL yourself. I’ve found this especially useful when programing a mass data correction utility and the timeframe to run it is limited. I’ve attached an example program which is self contained by using local classes. There are also some test classes for CL_ABAP_PARALLEL which provide useful examples.
REPORT zparallel_test.
CLASS main DEFINITION.
PUBLIC SECTION.
TYPES:
BEGIN OF shared_record,
process_mode TYPE c LENGTH 1,
END OF shared_record,
single_record TYPE scarr-carrid,
BEGIN OF task_result,
carrid TYPE scarr-carrid,
count TYPE i,
END OF task_result.
CLASS-METHODS process.
ENDCLASS.
CLASS single_task DEFINITION
INHERITING FROM cl_abap_parallel.
PUBLIC SECTION.
METHODS: do REDEFINITION.
ENDCLASS.
*This is the only statement outside of the classes
main=>process( ).
CLASS main IMPLEMENTATION.
METHOD process.
DATA: tasks_input TYPE cl_abap_parallel=>t_in_tab,
task_input_single TYPE xstring,
single_record TYPE single_record,
tasks_input_shared TYPE xstring,
shared_record TYPE shared_record,
task_result TYPE task_result.
shared_record-process_mode = 1.
* Since the process_mode value is shared across all tasks, store
* it in the shared variable (tasks_input_shared) instead of
* repeating it for every task input record (tasks_input)
* To be imported by SINGLE_TASK->DO
EXPORT buffer_task_shared = shared_record
TO DATA BUFFER tasks_input_shared.
* Get data to be processed. EXPORT each record and collect them all into
* table tasks_input.
SELECT
carrid
FROM scarr
INTO @single_record.
* To be imported by SINGLE_TASK->DO
EXPORT buffer_task = single_record TO DATA BUFFER task_input_single.
INSERT task_input_single INTO TABLE tasks_input.
ENDSELECT.
* Create the instance while configuring the resource usage
DATA(parallel) = NEW single_task(
* p_num_tasks = 8
* p_timeout = 200
* p_percentage = 50
* p_num_processes = 20
* p_local_server =
).
DATA(debug) = ' '.
* Perform the tasks in parallel
parallel->run(
EXPORTING
p_in_tab = tasks_input
p_in_all = tasks_input_shared
p_debug = debug
IMPORTING
p_out_tab = DATA(tasks_output)
).
LOOP AT tasks_output ASSIGNING FIELD-SYMBOL(<task_output_single>).
* Something went wrong, like a timeout, if the message has a value
IF <task_output_single>-message IS NOT INITIAL.
WRITE: / 'Task Error:', <task_output_single>-message.
ENDIF.
IF <task_output_single>-result IS NOT INITIAL.
* Exported from SINGLE_TASK->DO
IMPORT buffer_result = task_result
FROM DATA BUFFER <task_output_single>-result.
WRITE: / task_result-carrid, task_result-count.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
CLASS single_task IMPLEMENTATION.
METHOD do.
* I referenced the MAIN local class instead of using the data dictionary
* for the ease for copy/paste
DATA: shared_record TYPE main=>shared_record,
single_record TYPE main=>single_record,
task_result TYPE main=>task_result.
* Exported by MAIN->PROCCESS
IMPORT buffer_task_shared = shared_record FROM DATA BUFFER p_in_all.
* Exported by MAIN->PROCCESS
IMPORT buffer_task = single_record FROM DATA BUFFER p_in.
* Other shared values I like to include are a simulation/update flag
* and a level for the logging detail
CASE shared_record-process_mode.
WHEN '1'.
* Incredibly simplistic example which doesn't warrant parallel processing
SELECT
carrid,
COUNT( * )
FROM sflight
WHERE
carrid = @single_record
GROUP BY
carrid
INTO ( @task_result-carrid, @task_result-count ).
ENDSELECT.
WHEN '2'.
ENDCASE.
* To be imported by MAIN->PROCESS
EXPORT buffer_result = task_result TO DATA BUFFER p_out.
ENDMETHOD.
ENDCLASS.
No comments:
Post a Comment