Auch SAP kann Multitasking. Dazu muss mann einen RFC-fähigen Funktionsbaustein in einem neuen Task innerhalb einer sogenannten Taskgroup aufrufen. Die Abarbeitung erfolgt dann asynchron, dass Ergebnis wird in einer Handler-Funktion zurückgeliefert.
Im folgenden Funktionsbeispiel erzeugt das Rahmenprogramm “zparallel” Arbeitspakete (it_input) für die Tasks und ein Objekt der Klasse “lcl_task_manager”. Letzteres managed die Verteilung der Eingangsdaten (it_input) auf mehrere Tasks. Die Taskobjekte werden durch die Klasse “lcl_task” abgebildet. Die einzelnen Tasks rufen asynchron mit ihren Arbeitspaketen den RFC-fähigen Funktionsbaustein “BAPI_BILLINGDOC_GETLIST”.
Nach erfolgreichem Beenden aller Tasks werden die Ergebnisse der Arbeitspakete zusammengesetzt (it_result) und an das Rahmenprogramm zurückgegeben.
Weiterführende Informationen: Link, Link, Link und Link
Rahmenprogramm: REPORT zparallel (Aufruf der Parallelverarbeitung)
REPORT zparallel. * Klassen für Taskmanagement INCLUDE ztask_manager. * iTabs für Eingabe- und Ausgabedaten DATA: it_input TYPE lcl_task=>ty_it_input_data. DATA: it_result TYPE lcl_task_manager=>ty_it_final_result. DATA: it_statistic TYPE lcl_task_manager=>ty_it_statistic. PARAMETERS: p_rows TYPE i DEFAULT 1000. " Test-Elemente PARAMETERS: p_wait TYPE i DEFAULT 5. " Wartezeit in [s] PARAMETERS: p_max_rt TYPE i DEFAULT 3. " max. Anzahl Versuche PARAMETERS: p_size TYPE i DEFAULT 100. " Intervallgröße START-OF-SELECTION. DATA: it_vbelv TYPE STANDARD TABLE OF vbelv WITH DEFAULT KEY. * Intervalle erzeugen SELECT DISTINCT vbelv FROM vbfa INTO TABLE @it_vbelv UP TO @p_rows ROWS. IF sy-subrc = 0. SORT: it_vbelv BY table_line. DATA(lv_lines) = lines( it_vbelv ). DATA(lv_parts) = lv_lines DIV p_size. DATA(lv_mod) = lv_lines MOD p_size. IF lv_mod <> 0. lv_parts = lv_parts + 1. ENDIF. DATA(lv_idx_l) = 0. DATA(lv_idx_h) = 0. DO lv_parts TIMES. lv_idx_l = lv_idx_h + 1. lv_idx_h = lv_idx_l - 1 + p_size. IF lv_idx_h > lv_lines. lv_idx_h = lv_lines. ENDIF. APPEND VALUE #( sign = 'I' option = 'BT' ref_doc_low = it_vbelv[ lv_idx_l ] ref_doc_high = it_vbelv[ lv_idx_h ] ) TO it_input. ENDDO. * Referenz auf Tastmanager für Parallelverarbeitung DATA(o_task_manager) = lcl_task_manager=>get_instance( i_task_group = lcl_task_manager=>co_default_task_group ). DATA: lv_started TYPE i. DATA: lv_ended TYPE i. DATA: lv_missed TYPE i. * Timer starten DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ). DATA(usec_start) = o_timer->get_runtime( ). * Parallele Verarbeitung der Eingabedaten starten IF abap_true = o_task_manager->start_parallel_working( EXPORTING i_it_input_data = it_input i_wait_time_sec = p_wait i_max_retries = p_max_rt IMPORTING e_it_final_result = it_result e_it_statistic = it_statistic e_tasks_started = lv_started e_tasks_ended = lv_ended e_tasks_missed = lv_missed ). * Timer stoppen DATA(usec_end) = o_timer->get_runtime( ). DATA(usec) = CONV decfloat16( usec_end - usec_start ). DATA(sec) = usec / 1000000. WRITE: / ' Zeit [µs]:', usec. WRITE: / ' Zeit [s]:', sec. WRITE: / ' max. Tasks:', o_task_manager->get_max_tasks( ). WRITE: / ' free Tasks:', o_task_manager->get_free_tasks( ). WRITE: / 'Tasks started:', lv_started. WRITE: / ' Tasks ended:', lv_ended. WRITE: / ' Tasks missed:', lv_missed. WRITE: / ' Input lines:', lines( it_input ). WRITE: / ' Result lines:', lines( it_result ). ULINE. WRITE: / | Task # \| Retries \| Time [µs] \| S \| IN: LOW \| IN: HIGH \| SUBRC \| SIZE|. ULINE. LOOP AT it_statistic ASSIGNING FIELD-SYMBOL(<fs_stat>). WRITE: / <fs_stat>-task_number, '|', <fs_stat>-retries, '|', <fs_stat>-runtime, '|', <fs_stat>-successful, '|', <fs_stat>-input_data-ref_doc_low, '|', <fs_stat>-input_data-ref_doc_high, '|', <fs_stat>-subrc, '|', <fs_stat>-output_part_size. ENDLOOP. ULINE. WRITE: / | ITEM # \| IN: LOW \| IN: HIGH \| OUT: REF_DOC|. ULINE. * Ergebnisausgabe LOOP AT it_result ASSIGNING FIELD-SYMBOL(<fs_res>). WRITE: / sy-tabix, '|', <fs_res>-input_data-ref_doc_low, '|', <fs_res>-input_data-ref_doc_high, '|', <fs_res>-output_data-ref_doc. ENDLOOP. ENDIF. ENDIF.
Task-Klassen: INCLUDE ZTASK_MANAGER
*--------------------------------------------------------------------* * Task-Klasse (Worker) *--------------------------------------------------------------------* * Ruft asynchon einen RFC-fähigen Funktionsbaustein * und verarbeitet asynchron das Ergebnis (Event) *--------------------------------------------------------------------* CLASS lcl_task DEFINITION FINAL. PUBLIC SECTION. * Eingabedaten des Task TYPES: ty_input_data TYPE bapi_ref_doc_range. * Ausgabedaten des Task TYPES: ty_output_data TYPE bapivbrksuccess. TYPES: ty_it_input_data TYPE STANDARD TABLE OF ty_input_data WITH DEFAULT KEY. TYPES: ty_it_output_data TYPE STANDARD TABLE OF ty_output_data WITH DEFAULT KEY. * Name des RFC-fähigen Funktionsbausteines für "start_async_call" und "task_receive"-Handler CONSTANTS: co_fb_name TYPE string VALUE 'BAPI_BILLINGDOC_GETLIST'. * Event, wird getriggert, wenn die asynchrone Methode task_receive aufgerufen wird EVENTS: data_received EXPORTING VALUE(e_subrc) TYPE sy-subrc VALUE(e_runtime) TYPE int4 VALUE(e_retries) TYPE i VALUE(e_input_data) TYPE ty_input_data VALUE(e_output_data) TYPE ty_it_output_data. * Konstruktor METHODS: constructor IMPORTING i_task_group TYPE rzllitab-classname i_task_number TYPE i. * Startet asynchronen Aufruf des RFC-Bausteins METHODS: start_async_call IMPORTING i_input_data TYPE ty_input_data i_started_after_n_retries TYPE i RETURNING VALUE(rv_subrc) TYPE sy-subrc. * Handlermethode, wird asynchron gerufen METHODS: task_receive IMPORTING p_task TYPE clike. * Hilfsfunktionen METHODS: is_running RETURNING VALUE(rv_running) TYPE boolean. METHODS: get_group RETURNING VALUE(rv_group) TYPE rzllitab-classname. METHODS: get_task_number RETURNING VALUE(rv_number) TYPE i. PRIVATE SECTION. DATA: lv_group TYPE rzllitab-classname. DATA: lv_number TYPE i. DATA: lv_running TYPE boolean. DATA: lv_started_after_n_retries TYPE i. DATA: lv_start_time TYPE int4. DATA: lv_end_time TYPE int4. * Merker für Übergabe-Parameter in start_async_call DATA: lv_input_data TYPE ty_input_data. ENDCLASS. CLASS lcl_task IMPLEMENTATION. *--------------------------------------------------------------------* * Konstruktor *--------------------------------------------------------------------* * -> i_task_group - Gruppenname * -> i_task_number - Tasknummer *--------------------------------------------------------------------* METHOD constructor. lv_group = i_task_group. lv_number = i_task_number. lv_running = abap_false. lv_start_time = 0. lv_end_time = 0. lv_started_after_n_retries = 0. ENDMETHOD. *--------------------------------------------------------------------* * Startet asynchronen Aufruf des RFC-Bausteins *--------------------------------------------------------------------* * -> i_input_data - Eingabedaten für Verarbeitung * -> i_started_after_n_retries - Statistikwert, nach wieviel Versuchen * der Task gestartet werden konnte * <- rv_subrc - Status des asynchronen Aufrufs *--------------------------------------------------------------------* METHOD start_async_call. lv_running = abap_false. lv_started_after_n_retries = i_started_after_n_retries. GET RUN TIME FIELD lv_start_time. * neuen asynchronen Aufruf des Bausteines starten * Gruppe, Tasknummer und Handlerfunktion übergeben * Baustein muss RFC-fähig sein * Verfügbare RFC-Gruppen für Parallelverarbeitung: Transaktion RZ12 * Namen und Typen der EXPORTING-Parameter beachten: * -> fehlerhafte Angaben werden nicht vom Compiler bemerkt oder auch nicht als sy-subrc zurückgegeben CALL FUNCTION co_fb_name STARTING NEW TASK lv_number DESTINATION IN GROUP lv_group CALLING task_receive ON END OF TASK EXPORTING refdocrange = i_input_data EXCEPTIONS system_failure = 1 communication_failure = 2 resource_failure = 3 OTHERS = 4. * Fehlerstatus zurückgeben rv_subrc = sy-subrc. IF rv_subrc = 0. lv_input_data = i_input_data. lv_running = abap_true. ENDIF. ENDMETHOD. *--------------------------------------------------------------------* * Receive-Funktion, welche bei Fertigsstellung vom Task asynchron * getriggert wird * Es werden keine WRITE-Statements ausgeführt! *--------------------------------------------------------------------* * -> p_task - Tasknummer *--------------------------------------------------------------------* METHOD task_receive. DATA: it_task_data TYPE ty_it_output_data. * Ergebnisse des asynchronen RFC-Aufrufes vom Funktionsbaustein holen RECEIVE RESULTS FROM FUNCTION co_fb_name TABLES success = it_task_data EXCEPTIONS system_failure = 1 communication_failure = 2 resource_failure = 3 OTHERS = 4. GET RUN TIME FIELD lv_end_time. DATA(lv_runtime) = lv_end_time - lv_start_time. * Event für Datenrückgabe triggern RAISE EVENT data_received EXPORTING e_subrc = sy-subrc e_runtime = lv_runtime e_retries = lv_started_after_n_retries e_input_data = lv_input_data e_output_data = it_task_data. lv_running = abap_false. ENDMETHOD. *--------------------------------------------------------------------* * Gibt Status des Tasks *--------------------------------------------------------------------* * <- rv_running - Status des Tasks *--------------------------------------------------------------------* METHOD is_running. rv_running = lv_running. ENDMETHOD. *--------------------------------------------------------------------* * Gibt Gruppenname, in welcher der Task läuft *--------------------------------------------------------------------* * <- rv_group - Gruppenname *--------------------------------------------------------------------* METHOD get_group. rv_group = lv_group. ENDMETHOD. *--------------------------------------------------------------------* * Gibt Tasknummer *--------------------------------------------------------------------* * <- rv_number - Tasknummer *--------------------------------------------------------------------* METHOD get_task_number. rv_number = lv_number. ENDMETHOD. ENDCLASS. *--------------------------------------------------------------------* * Verwaltungs-Klasse für Tasks *--------------------------------------------------------------------* * 1. Verarbeitet Eingabedaten (Workitems) * 2. Holt für jedes Workitem einen freien Task * 3. Startet pro Workitem einen Task (asynchron) * 4. Wertet das asynchron gelieferte Ergebis in einer Handlermethode aus *--------------------------------------------------------------------* * Sinnvoll ist die Implementierung nur, wenn die Workprozesse * ausreichend lange dauern, so dass sich eine parallele Verarbeitung * überhaupt lohnt *--------------------------------------------------------------------* CLASS lcl_task_manager DEFINITION FINAL CREATE PRIVATE. PUBLIC SECTION. * Datentyp für finale Ausgabe, wenn alle Workprozesse fertig sind TYPES: BEGIN OF ty_final_result, input_data TYPE lcl_task=>ty_input_data, output_data TYPE lcl_task=>ty_output_data, END OF ty_final_result. TYPES: ty_it_final_result TYPE STANDARD TABLE OF ty_final_result WITH DEFAULT KEY. TYPES: BEGIN OF ty_statistic, task_number TYPE i, runtime TYPE int4, retries type i, input_data TYPE lcl_task=>ty_input_data, output_part_size TYPE i, subrc TYPE sy-subrc, successful TYPE boolean, END OF ty_statistic. TYPES: ty_it_statistic TYPE STANDARD TABLE OF ty_statistic WITH DEFAULT KEY. * RFC-Gruppenname * die Gruppe mit classname = 'parallel_generators' wird standardmäßig von Admins eingerichtet * Gruppen sind in Tabelle "RZLLITAB" abgelegt, grouptype = 'S' (Server) * Transaktion RZ12 CONSTANTS: co_default_task_group TYPE rzllitab-classname VALUE 'parallel_generators'. * Factory-Funktion CLASS-METHODS: get_instance IMPORTING i_task_group TYPE rzllitab-classname DEFAULT co_default_task_group RETURNING VALUE(rv_ref_task_manager) TYPE REF TO lcl_task_manager. * Startet die parallele Abarbeitung der Eingabedaten METHODS: start_parallel_working IMPORTING i_it_input_data TYPE lcl_task=>ty_it_input_data i_wait_time_sec TYPE i DEFAULT 10 i_max_retries TYPE i DEFAULT 3 EXPORTING e_it_final_result TYPE ty_it_final_result e_it_statistic TYPE ty_it_statistic e_tasks_started TYPE i e_tasks_ended TYPE i e_tasks_missed TYPE i RETURNING VALUE(rv_work_complete) TYPE boolean. METHODS: get_max_tasks RETURNING VALUE(rv_max_tasks) TYPE i. METHODS: get_free_tasks RETURNING VALUE(rv_free_tasks) TYPE i. PRIVATE SECTION. TYPES: BEGIN OF ty_task, task_number TYPE i, o_task TYPE REF TO lcl_task, END OF ty_task. * Null-Referenz CONSTANTS: null TYPE REF TO lcl_task_manager VALUE IS INITIAL. * Referenz auf ein Task-Objekt CLASS-DATA: o_task_manager TYPE REF TO lcl_task_manager. CLASS-DATA: lv_max_tasks TYPE i. CLASS-DATA: lv_free_tasks TYPE i. * Zähler CLASS-DATA: lv_tasks_started TYPE i. CLASS-DATA: lv_tasks_ended TYPE i. CLASS-DATA: lv_active_tasks TYPE i. * globale Taskliste CLASS-DATA: it_tasks TYPE SORTED TABLE OF ty_task WITH UNIQUE KEY task_number. * Ergebnisliste CLASS-DATA: it_final_result TYPE ty_it_final_result. * Ergebnisliste CLASS-DATA: it_statistic TYPE ty_it_statistic. * Handler für data_received-Ereignis der Tasks METHODS: on_task_completed FOR EVENT data_received OF lcl_task IMPORTING e_subrc e_runtime e_retries e_input_data e_output_data sender. * Hilfsfunktionen METHODS: get_free_task RETURNING VALUE(rv_task_number) TYPE i. METHODS: get_task IMPORTING i_task_number TYPE i RETURNING VALUE(rv_ref_task) TYPE REF TO lcl_task. METHODS: start_task IMPORTING i_input_data TYPE lcl_task=>ty_input_data i_wait_time_sec TYPE i DEFAULT 10 i_max_retries TYPE i DEFAULT 3 RETURNING VALUE(rv_ok) TYPE boolean. ENDCLASS. *--------------------------------------------------------------------* CLASS lcl_task_manager IMPLEMENTATION. *--------------------------------------------------------------------* * Factory Methode für Singleton (genau eine Referenz) auf den * Taskmanager zurück *--------------------------------------------------------------------* * -> i_task_group - Name der RFC-Task-Group, in der die Tasks ablaufen * sollen * Vorgabewert ist 'parallel_generators', welche * standardmäßig von Admins eingerichtet wird * Gruppen sind in Tabelle "RZLLITAB" abgelegt, * grouptype = 'S' (Server) * Transaktion RZ12 * <- rv_ref_task_manager - Referenz auf Task-Manager-Objekt *--------------------------------------------------------------------* METHOD get_instance. rv_ref_task_manager = null. IF o_task_manager IS INITIAL. DATA(lv_task_group) = i_task_group. lv_max_tasks = 0. lv_free_tasks = 0. IF lv_task_group IS INITIAL. * falls keine task group übergeben wurde, eine heraussuchen * Verfügbare RFC-Gruppen für Parallelverarbeitung: Transaktion RZ12 SELECT SINGLE classname FROM rzllitab INTO @lv_task_group WHERE grouptype = 'S'. ENDIF. IF NOT lv_task_group IS INITIAL. * Start neue LUW COMMIT WORK. * Parallel Background Tasks - Initialisierung der PBT-Umgebung * Sollte nur 1x aufgerufen werden, sonst Exception "pbt_env_already_initialized" CALL FUNCTION 'SPBT_INITIALIZE' EXPORTING group_name = lv_task_group IMPORTING max_pbt_wps = lv_max_tasks free_pbt_wps = lv_free_tasks EXCEPTIONS invalid_group_name = 1 internal_error = 2 pbt_env_already_initialized = 3 currently_no_resources_avail = 4 no_pbt_resources_found = 5 cant_init_different_pbt_groups = 6 OTHERS = 7. * wenn Initialisierung ok IF sy-subrc = 0. * wenn genug freie Tasks vorhanden IF lv_free_tasks >= 1. o_task_manager = NEW #( ). lv_tasks_started = 0. lv_tasks_ended = 0. lv_active_tasks = 0. CLEAR: it_tasks. * Anzahl freie Tasks zur Liste hinzufügen DO lv_free_tasks TIMES. DATA(o_task) = NEW lcl_task( i_task_number = sy-index i_task_group = lv_task_group ). * Eventhandler für beendete Tasks SET HANDLER o_task_manager->on_task_completed FOR o_task. APPEND VALUE #( task_number = sy-index o_task = o_task ) TO it_tasks. ENDDO. rv_ref_task_manager = o_task_manager. ENDIF. ENDIF. ENDIF. ELSE. rv_ref_task_manager = o_task_manager. ENDIF. ENDMETHOD. *--------------------------------------------------------------------* * Parallele / asynchrone Abarbeitung anschieben *--------------------------------------------------------------------* * -> i_it_input_data - Eingabedaten * -> i_wait_time_sec - Wartezeit für Abarbeitung aller Tasks in sec * -> i_max_retries - max. Anz. Versuche * <- e_it_final_result - Erbebnismenge der parallelen Verarbeitung * <- e_tasks_started - Anz. gestartete Tasks * <- e_tasks_ended - Anz. beendete Tasks (sollte im Idealfall * e_tasks_started entsprechen * <- e_tasks_missed - Anz. Tasks, die nicht gestartet werden * konnten *--------------------------------------------------------------------* METHOD start_parallel_working. rv_work_complete = abap_false. lv_tasks_started = 0. lv_tasks_ended = 0. lv_active_tasks = 0. CLEAR: it_final_result. CLEAR: it_statistic. DATA(lv_tasks_missed) = 0. * Übergebene Workitems abarbeiten LOOP AT i_it_input_data ASSIGNING FIELD-SYMBOL(<fs_input_data>). * pro Workitem einen Task (asynchron) starten IF me->start_task( i_input_data = <fs_input_data> i_max_retries = i_max_retries ). ELSE. * Fehler: kein Task für Workitem verfügbar lv_tasks_missed = lv_tasks_missed + 1. APPEND VALUE #( task_number = -1 runtime = 0 retries = i_max_retries input_data = <fs_input_data> output_part_size = 0 subrc = -1 successful = abap_false ) TO it_statistic. ENDIF. ENDLOOP. * warten bis alle Tasks fertig sind WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_active_tasks = 0 ) UP TO i_wait_time_sec SECONDS. IF sy-subrc = 0. rv_work_complete = abap_true. ENDIF. * Ergebnisrückgabe e_it_final_result = it_final_result. e_it_statistic = it_statistic. e_tasks_started = lv_tasks_started. e_tasks_ended = lv_tasks_ended. e_tasks_missed = lv_tasks_missed. ENDMETHOD. *--------------------------------------------------------------------* * Gibt Anzahl max. Tasks *--------------------------------------------------------------------* * <- rv_max_tasks - Anzahl max. Tasks *--------------------------------------------------------------------* METHOD get_max_tasks. rv_max_tasks = lv_max_tasks. ENDMETHOD. *--------------------------------------------------------------------* * Gibt Anzahl freie Tasks *--------------------------------------------------------------------* * <- rv_free_tasks - Anzahl freie Tasks *--------------------------------------------------------------------* METHOD get_free_tasks. rv_free_tasks = lv_free_tasks. ENDMETHOD. *--------------------------------------------------------------------* * Einen Task starten *--------------------------------------------------------------------* * -> i_input_data - Eingabedaten für die Verarbeitung * -> i_wait_time_sec - Wartezeit für Abarbeitung aller Tasks in sec * -> i_max_retries - max. Anz. Versuche wenn System "busy" bis * - Abbruch erfolgt * <- rv_ok - true, wenn Task gestartet werden konnte *--------------------------------------------------------------------* METHOD start_task. rv_ok = abap_false. DATA(lv_retries) = 0. * n Neuversuche einen freien Task zu bekommen WHILE lv_retries < i_max_retries. DATA(lv_tasknumber) = me->get_free_task( ). IF lv_tasknumber <> -1. DATA(o_task) = me->get_task( lv_tasknumber ). IF o_task IS BOUND. DATA(lv_subrc) = o_task->start_async_call( i_input_data = i_input_data i_started_after_n_retries = lv_retries ). CASE lv_subrc. WHEN 0. * Ok lv_tasks_started = lv_tasks_started + 1. lv_active_tasks = lv_active_tasks + 1. lv_retries = i_max_retries. rv_ok = abap_true. WHEN 1 OR 2. * Failure lv_retries = i_max_retries. WHEN 3. * Busy IF lv_tasks_started > 0. WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_tasks_ended >= lv_tasks_started ) UP TO i_wait_time_sec SECONDS. ENDIF. IF sy-subrc = 0. lv_retries = lv_retries + 1. ELSE. lv_retries = i_max_retries. ENDIF. WHEN OTHERS. * Sollte nicht auftreten lv_retries = i_max_retries. ENDCASE. ELSE. * Task not found (sollte nicht vorkommen) lv_retries = lv_retries + 1. ENDIF. ELSE. * kein freier Task verfügbar -> warten WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_tasks_ended >= lv_tasks_started ) UP TO i_wait_time_sec SECONDS. IF sy-subrc = 0. lv_retries = lv_retries + 1. ELSE. lv_retries = i_max_retries. ENDIF. ENDIF. ENDWHILE. ENDMETHOD. *--------------------------------------------------------------------* * Freien Task aus der Liste der verfügbaren suchen *--------------------------------------------------------------------* * <- rv_task_number - Tasknummer des gefundenen Tasks *--------------------------------------------------------------------* METHOD get_free_task. rv_task_number = -1. LOOP AT it_tasks ASSIGNING FIELD-SYMBOL(<fs_task>). IF <fs_task>-o_task->is_running( ) = abap_false. rv_task_number = <fs_task>-task_number. EXIT. ENDIF. ENDLOOP. ENDMETHOD. *--------------------------------------------------------------------* * Referenz auf Task anhand der Tasknummer holen *--------------------------------------------------------------------* * -> i_task_number - Tasknummer * <- rv_ref_task - Referenz auf Task *--------------------------------------------------------------------* METHOD get_task. IF line_exists( it_tasks[ task_number = i_task_number ] ). ASSIGN it_tasks[ task_number = i_task_number ] TO FIELD-SYMBOL(<fs_task>). IF <fs_task> IS ASSIGNED. rv_ref_task = <fs_task>-o_task. ENDIF. ENDIF. ENDMETHOD. *--------------------------------------------------------------------* * Eventhandler, wenn Task ferig *--------------------------------------------------------------------* * -> e_input_data - Eingabedaten des Tasks * -> e_output_data - Ausgabedaten des Tasks * -> sender - Referenz auf Task *--------------------------------------------------------------------* METHOD on_task_completed. * Zähler setzen lv_tasks_ended = lv_tasks_ended + 1. lv_active_tasks = lv_active_tasks - 1. APPEND VALUE #( task_number = sender->get_task_number( ) runtime = e_runtime retries = e_retries input_data = e_input_data output_part_size = lines( e_output_data ) subrc = e_subrc successful = abap_true ) TO it_statistic. * Hier Daten an Ergebnistabelle einfach anfügen LOOP AT e_output_data ASSIGNING FIELD-SYMBOL(<fs_output_data>). APPEND VALUE #( input_data = e_input_data output_data = <fs_output_data> ) TO it_final_result. ENDLOOP. ENDMETHOD. ENDCLASS.