ABAP Tipp - Wait for Task
Heute einmal einen Tipp für die asynchrone Verarbeitung oder wenn ihr in speziellen Situationen die Verarbeitung in einen separaten Task geben wollt. Wie geht es im Anschluss weiter?
Inhaltsverzeichnis
Hast du dir schon einmal die Frage gestellt, ob du auch auf einen Prozess warten kannst, wenn du diesen in einen asynchronen Task verschoben hast? In manchen Situationen geht es auch nicht anders, weil die Coding-Stelle keinen Commit zulässt oder es vorschreibt. In dem heutigen Beispiel wollen wir, dir zeigen, wie du auf den Prozess wartest und dann auf diesen Daten weiter arbeiten kannst.
Funktionsbaustein
Zur Vorbereitung benötigen wir einen Funktionsbaustein der die Aufgabe durchführt und zum Nachvollziehen dient. Dieser bekommt ein paar Import-Parameter, um eine Dummy Schnittstelle darzustellen. Weiterhin warten wir in dem Funktionsbaustein 5 Sekunden, bevor die Verarbeitung fortsetzt, um so eine lange Verarbeitung zu simulieren.
FUNCTION Z_60BS_TEST_ASYNC_FUNCTION
IMPORTING
VALUE(ID_FLAG) TYPE ABAP_BOOLEAN
VALUE(ID_USER) TYPE SYUNAME
VALUE(IT_DATA) TYPE STRING_TABLE.
WAIT UP TO 5 SECONDS.
ENDFUNCTION.
Da bei nicht vergessen, dass der Funktionsbaustein RFC-fähig sein muss, damit dieser in einem eigenen Task gestartet werden kann. Hier einmal der Auszug aus Eclipse, hier kannst du dies über den "Properties" View einstellen.
Testaufbau
Für den die Validierung des Ergebnis setzen wir auf eine zeitliche Messung, um festzustellen, ob der Prozess auf unserern parallelen Task wartet. Wir starten vor dem Aufruf des Funktionsbausteins den Timer und merken uns die Startzeit. Als Nächstes rufen wir den Funktionsbaustein mit dem Zusatz STARTING NEW TASK, um den Prozess asynchron in einem neuen Prozess zu starten. Am Ende berechnen wir die Differenz und schauen uns das Ergebnis an. Der Funktionsbaustein sollte ja 5 Sekunden lang laufen, bevor er sich beendet.
DATA:
ld_task TYPE char32 VALUE 'TASK_01'.
DATA(lo_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(ld_start) = lo_timer->get_runtime( ).
CALL FUNCTION 'Z_BS_TEST_ASYNC_FUNCTION'
STARTING NEW TASK ld_task
EXPORTING
id_flag = abap_true
id_user = sy-uname
it_data = VALUE string_table( ( `A` ) ( `B` ) ( `C` ) ).
out->write( |Process completed: { lo_timer->get_runtime( ) - ld_start }| ).
Die Verarbeitung endet bereits nach wenigen Millisekunden, der Baustein wir in einem eigenen Task beendet. Wenn wir also auf das Ende und mögliche Daten aus dem Funktionsbaustein warten, dann haben wir keine Chance darauf zu reagieren.
Lösung
Was kannst du in diesem Fall tun, um auf das Ergebnis zu reagieren? Wir müssen den Funktionsbaustein im ersten Schritt um einen Callback erweitern, sodass wir in unserer aktuellen Verarbeitung mitbekommen, wenn der Baustein fertig ist. Dazu verwenden wir den Zusatz CALLING ... ON END OF TASK und geben eine Methode an, die in unserer Klasse am Ende der Verarbeitung aufgerufen wird.
CALL FUNCTION 'Z_BS_TEST_ASYNC_FUNCTION'
STARTING NEW TASK ld_task
CALLING finished ON END OF TASK
EXPORTING
id_flag = abap_true
id_user = sy-uname
it_data = VALUE string_table( ( `A` ) ( `B` ) ( `C` ) ).
Die Methode benötigt einen Eingangsparameter mit dem Namen P_TASK, dieser enthält den Wert, den du beim Aufruf mitgegeben hast, damit du den zurückkommenden Prozess identifizieren kannst.
METHODS:
finished IMPORTING p_task TYPE char32.
Wir wissen also, dass der Prozess sich zurückmeldet, wenn die Verarbeitung abgeschlossen ist. Was nun noch benötigt wird, ist die Verarbeitung solange zu halten, bis die Rückmeldung durch den Funktionsbaustein erfolgt ist. Dabei kommt ein WAIT mit Zusatz zum Tragen. In diesem Fall gibt es in der Klasse eine Membervariable, die wir setzen, sobald der Task beendet wurde.
WAIT FOR ASYNCHRONOUS TASKS UNTIL md_finished = abap_true.
Wenn wir nun noch einmal die Klasse ausführen, dann wird sich nun ein anderes Ergebnis zeigen. Der Report wartet ca. 5 Sekunden, bevor die Messung stoppt und die weitere Verarbeitung wieder aufgenommen wird.
Beispiel
Hier noch einmal das vollständige Beispiel der Consolen Applikation mit den entsprechenden Implementierungen für das Beispiel.
CLASS zcl_test_async_start DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES: if_oo_adt_classrun.
DATA:
md_finished TYPE abap_bool.
METHODS:
finished
IMPORTING
p_task TYPE char32.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_test_async_start IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA:
ld_task TYPE char32 VALUE 'TASK_01'.
DATA(lo_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(ld_start) = lo_timer->get_runtime( ).
CALL FUNCTION 'Z_BS_TEST_ASYNC_FUNCTION'
STARTING NEW TASK ld_task
CALLING finished ON END OF TASK
EXPORTING
id_flag = abap_true
id_user = sy-uname
it_data = VALUE string_table( ( `A` ) ( `B` ) ( `C` ) ).
WAIT FOR ASYNCHRONOUS TASKS UNTIL md_finished = abap_true.
out->write( |Process completed: { lo_timer->get_runtime( ) - ld_start }| ).
ENDMETHOD.
METHOD finished.
md_finished = abap_true.
ENDMETHOD.
ENDCLASS.
Fazit
Wie wartet man also, bis der Prozess abgeschlossen wurde? Mit unserem Beispiel sollte das kein Problem mehr sein und du kannst damit noch etwas weiter experimentieren. Wie verhält es sich mit einem Commit im Funktionsbaustein und wie reagiert unser wartendes Programm darauf? Sicherlich auch ein paar spannende Fragen.