Заинтересовала возможность распараллеливания работы отчетов и решил переделать один отчет HR, который последовательно обрабатывает большое количество сотрудников. За основу взял эту статью.
Отчет представлял собой класс с главным методом, на вход которому подавался табельный номер сотрудника. Данный метод собирал всю необходимую информацию и на добавлял её в таблицу с агрегированными данными.
В процессе первой итерации распараллеливания преобразуем программу таким образом, что бы главный метод возвращал структуру с собранными данными. Таким образом параллельные процессы вернут таблицу с данными по каждому сотруднику. Затем в основном потоке останется агрегировать эти информацию в результирующую таблицу и отобразить её.
Таким образом в блоке START-OF-SELECTION собираем выбранные табельные номера:
1 2 3 4 5 6 7 8 |
START-OF-SELECTION gs_context-sel = VALUE #( begda = pn-begda endda = pn-endda category1 = so_category1 category2 = so_category2 ). GET peras. INSERT VALUE #( pernr = peras-pernr ) INTO TABLE gs_context-pernr. END-OF-SELECTION. |
Кроме того в структуру gs_context сохраняем значения с селекционного экрана, так как параллельные потоки их видеть не будут.
callback-подпрограмма before_rfc остается почти без изменений — передаем каждому потоку данные с селекционного экрана и его часть табельных номеров.
В подпрограмме in_rfc последовательно вызываем метод main исходной программы и возвращаемую структуру передаем назад в основной поток программы
Подпрограмма after_rfc вообще осталась без изменений — результат каждого блока собираем глобальную таблицу.
В основном потоке программы осталось собрать полученные данные глобальной таблицы в результирующую для дальнейшего вывода пользователю.
Таким образом основной блок программы представляет собой следующее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
TYPES: BEGIN OF ty_pernr, pernr TYPE pernr_d, END OF ty_pernr, BEGIN OF ty_context, BEGIN OF sel, " Данные с селекционного экрана begda TYPE begda, endda TYPE endda, cat TYPE ty_category, progname TYPE syst_cprog, END OF sel, pernr TYPE SORTED TABLE OF ty_pernr WITH UNIQUE KEY pernr, END OF ty_context. DATA: gs_context TYPE ty_context, gt_list TYPE zlist. " Тип таблицы отчета CONSTANTS: gc_serv_group TYPE spta_rfcgr VALUE 'parallel_generators'. " название группы серверов - ведется в тр RZ12 INITIALIZATION. go_app = NEW lcl_main( ). CLEAR gs_context. START-OF-SELECTION. GET peras. " Собираем выбранные табельные номера APPEND VALUE #( pernr = peras-pernr ) TO gs_context-pernr. * go_app->pernr_cost( iv_pernr = peras-pernr ). " Старая версия программы END-OF-SELECTION. " Копируем данные с селекционного экрана для потоков gs_context-sel = VALUE #( begda = pn-begda endda = pn-endda. cat = VALUE #( so_rab1 = VALUE #( FOR ls_rab1 IN so_rab1 ( ls_rab1 ) ) so_rab2 = VALUE #( FOR ls_rab2 IN so_rab2 ( ls_rab2 ) ) so_rab3 = VALUE #( FOR ls_rab3 IN so_rab3 ( ls_rab3 ) ) so_rab4 = VALUE #( FOR ls_rab4 IN so_rab4 ( ls_rab4 ) ) ) ). CALL FUNCTION 'SPTA_PARA_PROCESS_START_2' EXPORTING server_group = gc_serv_group * max_no_of_tasks = before_rfc_callback_form = 'BEFORE_RFC' in_rfc_callback_form = 'IN_RFC' after_rfc_callback_form = 'AFTER_RFC' callback_prog = sy-repid CHANGING user_param = gs_context EXCEPTIONS invalid_server_group = 1 no_resources_available = 2 OTHERS = 3. IF sy-subrc <> 0. MESSAGE e001. ENDIF. go_app->collect_output( gt_list ). go_app->excel_out( ). FORM get_max_tasks CHANGING cv_max_tasks TYPE i. STATICS: sv_max_tasks TYPE i. DATA: lv_free_pbt_wps TYPE i. IF sv_max_tasks IS NOT INITIAL. cv_max_tasks = sv_max_tasks. RETURN. ENDIF. CALL FUNCTION 'SPBT_INITIALIZE' EXPORTING group_name = gc_serv_group IMPORTING * MAX_PBT_WPS = free_pbt_wps = lv_free_pbt_wps 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. IF sy-subrc EQ 0. * Будем использовать 40% диалоговых процессов от доступных в текущий момент cv_max_tasks = sv_max_tasks = lv_free_pbt_wps * 40 / 100. ELSE. cv_max_tasks = sv_max_tasks = 5. ENDIF. ENDFORM. "get_max_tasks FORM before_rfc USING p_before_rfc_imp TYPE spta_t_before_rfc_imp "#EC NEEDED CHANGING p_before_rfc_exp TYPE spta_t_before_rfc_exp pt_rfcdata TYPE spta_t_indxtab p_failed_objects TYPE spta_t_failed_objects "#EC NEEDED p_objects_in_process TYPE spta_t_objects_in_process "#EC NEEDED p_user_param TYPE ty_context. "#EC CALLED DATA: lv_max_tasks TYPE i, ls_cond TYPE ty_context, lt_task_pernr TYPE SORTED TABLE OF ty_pernr WITH UNIQUE KEY pernr. STATICS: sv_package_size TYPE i. IF p_user_param-pernr IS INITIAL. p_before_rfc_exp-start_rfc = space. RETURN. ENDIF. PERFORM get_max_tasks CHANGING lv_max_tasks. IF sv_package_size IS INITIAL. DATA(lv_tab_lines) = lines( p_user_param-pernr ). ENDIF. " Размер пачки на задачу sv_package_size = lv_tab_lines / lv_max_tasks. IF sv_package_size = 0. sv_package_size = 1. ENDIF. DATA(lv_cnt) = 0. LOOP AT p_user_param-pernr ASSIGNING FIELD-SYMBOL(<fs_pernr>). IF lv_cnt < sv_package_size. APPEND <fs_pernr> TO lt_task_pernr. DELETE p_user_param-pernr INDEX 1. lv_cnt = lv_cnt + 1. ELSE. EXIT. ENDIF. ENDLOOP. " Остаток в последнюю пачку IF lt_task_pernr IS INITIAL AND lines( p_user_param-pernr ) < sv_package_size. LOOP AT p_user_param-pernr ASSIGNING <fs_pernr>. APPEND <fs_pernr> TO lt_task_pernr. DELETE p_user_param-pernr INDEX 1. ENDLOOP. ENDIF. IF lt_task_pernr IS INITIAL. p_before_rfc_exp-start_rfc = space. RETURN. ENDIF. ls_cond = p_user_param. ls_cond-pernr = lt_task_pernr. " Упаковываем данные для использования в in_rfc CALL FUNCTION 'SPTA_INDX_PACKAGE_ENCODE' EXPORTING data = ls_cond IMPORTING indxtab = pt_rfcdata. p_before_rfc_exp-start_rfc = abap_true. ENDFORM. FORM in_rfc USING p_in_rfc_imp TYPE spta_t_in_rfc_imp "#EC NEEDED CHANGING p_in_rfc_exp TYPE spta_t_in_rfc_exp "#EC NEEDED p_rfcdata TYPE spta_t_indxtab. "#EC CALLED DATA: ls_taskdata_in TYPE ty_context, lt_taskdata_out TYPE zumz_2539_t. * Распаковываем данные из before_rfc CALL FUNCTION 'SPTA_INDX_PACKAGE_DECODE' EXPORTING indxtab = p_rfcdata IMPORTING data = ls_taskdata_in. DATA(lo_main) = NEW lcl_main( ). * Обрабатываем каждый табельный номер LOOP AT ls_taskdata_in-pernr ASSIGNING FIELD-SYMBOL(<fs_taskdata>). APPEND lo_main->pernr_cost( iv_begda = ls_taskdata_in-sel-begda iv_endda = ls_taskdata_in-sel-endda iv_progname = ls_taskdata_in-sel-progname is_cat = ls_taskdata_in-sel-cat iv_pernr = <fs_taskdata>-pernr ) TO lt_taskdata_out. ENDLOOP. * Запаковываем для использования в after_rfc CALL FUNCTION 'SPTA_INDX_PACKAGE_ENCODE' EXPORTING data = lt_taskdata_out IMPORTING indxtab = p_rfcdata. ENDFORM. "in_rfc FORM after_rfc USING p_rfcdata TYPE spta_t_indxtab p_rfcsubrc TYPE sy-subrc p_rfcmsg TYPE spta_t_rfcmsg "#EC NEEDED p_objects_in_process TYPE spta_t_objects_in_process "#EC NEEDED p_after_rfc_imp TYPE spta_t_after_rfc_imp "#EC NEEDED CHANGING p_after_rfc_exp TYPE spta_t_after_rfc_exp "#EC NEEDED p_user_param. "#EC CALLED "#EC NEEDED DATA: lt_taskdata TYPE lcl_main=>tt_data. IF p_rfcsubrc IS INITIAL. CALL FUNCTION 'SPTA_INDX_PACKAGE_DECODE' EXPORTING indxtab = p_rfcdata IMPORTING data = lt_taskdata. APPEND LINES OF lt_taskdata TO gt_list. EXIT. ENDIF. ENDFORM. "after_rfc |
Остается только немного поправить, что бы каждый поток возвращал частично агрегированные записи и выполнять только окончательное объединение в основном потоке.