
SAP ABAP 多线程还在用老掉牙的 NEW TASK RFC看完你会删掉那堆 WAIT UNTIL副标题第一次学并行先让 10 个快递同时盖章再套到批量 BAPI。读者会写内表、LOOP、CALL FUNCTION还在用CALL FUNCTION ... STARTING NEW TASK自己数任务、自己WAIT UNTIL。读完你能丢掉那堆 RFC 样板用CL_ABAP_PARALLEL拆包并行并看懂物料扩展程序ZMMU002S为什么那样写。建议先只看第 16 节。看懂「10 个快递同时签收」再往后翻。第 7 节以后是「套到真实程序」和附录。1. 先用大白话说清楚1.1 你现在的程序多半是这样LOOP AT 内表. 调 BAPI 处理这一行. 大约 1 秒 ENDLOOP.1 万行就是大约 1 万秒。人在屏幕前干等。1.2 「并行」想做的事不要一条接一条。把 1 万行切成 10 包每包 1000 行让系统里空闲的对话窗口进程各拿一包同时干。打个比方生活程序你自己做 1000 份盒饭一个 LOOP 串行跑 BAPI请 5 个厨师每人 200 份5 个对话进程各处理一包你是领班分完盘子站着等全做完CL_ABAP_PARALLEL负责分发和等待ABAP 里说的「多线程」不是Windows 那种线程。它是再开几个对话工作进程DIA每个进程跑你的同一段逻辑只是数据不同。对话工作进程可以先记成系统里那种平时给你跑前台程序的工人。闲着的时候可以借来干活。1.3 什么时候值得并行同时满足这三条再考虑活很重一条就要零点几秒到几秒BAPI、远程接口。行和行基本不相干A 物料扩工厂不需要等 B 物料先成功。一次数量大几百、几千、几万行。下面这些不要并行循环里只是给字段赋值、改个字符串太快分发的时间比干活还长第 2 行必须用第 1 行刚写进数据库的结果必须弹 GUI、读 Excel、GUI_UPLOAD并行工人没有 SAP 窗口2. SAP 已经写好了「领班」以前要自己写RFC 函数、STARTING NEW TASK、自己数开了几个、自己WAIT、自己收结果。现在用一个标准类就行CL_ABAP_PARALLEL你只需要做两件事写一个小类说明「一包数据怎么处理」。这个类要实现接口IF_ABAP_PARALLEL里面只有一个方法名字叫DO。在主程序里把数据切成几包每包NEW一个小类交给RUN_INST然后等。三个名字先混个脸熟后面例子会反复出现名字把它当成干什么CL_ABAP_PARALLEL领班决定请几个厨师、发盘子、等收工IF_ABAP_PARALLEL厨师手册封面规定厨师必须会做的那一道工序DO你写的本地类比如LCL_TASK一个厨师 他手上那一笼菜属性里放这一包数据DO里炒这一包DO没有IMPORTING/EXPORTING。这一包的输入、输出全部放在对象的属性DATA里。领班会把整个对象「打包寄」到另一个进程那边拆开再调用DO。「打包寄」的术语叫序列化。第一次听只记一句对象必须能被复印成一串字节寄走。所以属性里只能放数字、字符串、内表、结构不能放 GUI 控件、Excel OLE 这种带窗口的东西。接口源码一共就这么几行没有隐藏魔法INTERFACE if_abap_parallel PUBLIC. INTERFACES if_serializable_object. 声明我可以被打包寄走 METHODS do. 到了那边就调这个方法 ENDINTERFACE.3. 先抄一个能跑的最小例子请务必跑一遍先不要想物料、BAPI。想象网点柜台上堆了10 个快递你一个人盖章盖完 1 号再盖 2 号一个 2 秒一共 20 秒。请几个仓管同时盖10 个件差不多同一时间盖完大约 2 秒出头。程序要做的就是这件事每个快递一个任务任务里假装忙 2 秒然后写上「已签收」。在测试系统新建可执行程序整段粘贴、激活、F8。跑的时候旁边开事务SM50能看到好几个人同时在盖章。REPORT z_para_hello. ------------------------------------------------- 1一个对象 一个快递 单号是输入签收结果是输出 ------------------------------------------------- CLASS lcl_express DEFINITION. PUBLIC SECTION. INTERFACES if_abap_parallel. METHODS constructor IMPORTING iv_bill TYPE i. 创建时把单号塞进来 DATA mv_bill TYPE i. 这件货的单号比如 1、2、3 DATA mv_msg TYPE string. 盖完章后写的话比如「单号3已签收」 ENDCLASS. CLASS lcl_express IMPLEMENTATION. METHOD constructor. mv_bill iv_bill. 先记下来先不干活 ENDMETHOD. METHOD if_abap_parallel~do. 这句话运行在「另一个工人」身上不是你当前这个窗口 WAIT UP TO 2 SECONDS. 假装扫码、盖章故意慢一点方便你去 SM50 看 mv_msg |单号{ mv_bill }已签收|. ENDMETHOD. ENDCLASS. ------------------------------------------------- 2你当调度把 10 个件分出去然后等全盖完 ------------------------------------------------- START-OF-SELECTION. DATA lt_in TYPE cl_abap_parallelt_in_inst_tab. 待分出去的快递清单 DATA lt_out TYPE cl_abap_parallelt_out_inst_tab. 盖完拿回来的回执 DATA lo_one TYPE REF TO lcl_express. 柜台上放 10 个件单号 110。这时还没人盖章 DO 10 TIMES. APPEND NEW lcl_express( sy-index ) TO lt_in. ENDDO. 开始分件。程序会停在这一行直到 10 个都盖完或有人失手 NEW cl_abap_parallel( p_percentage 50 )-run_inst( EXPORTING p_in_tab lt_in IMPORTING p_out_tab lt_out ). 把回执打在屏幕上 LOOP AT lt_out INTO DATA(ls_out). IF ls_out-message IS NOT INITIAL OR ls_out-inst IS NOT BOUND. WRITE: / 第, ls_out-index, 个件没盖成, ls_out-message. CONTINUE. ENDIF. lo_one ? ls_out-inst. 回执上只写「一个工人」转回我们的快递对象才能看单号 WRITE: / lo_one-mv_msg, 花了, ls_out-time, 秒. ENDLOOP.正常的话大约 24 秒整屏刷完不是 20 秒类似单号1已签收 花了 2 秒 单号2已签收 花了 2 秒 ... 单号10已签收 花了 2 秒如果还是要等大约 20 秒说明几乎没请到帮手去 SM50 看是不是只有一条对话进程。3.1 对着代码看就三件事创建的时候不盖章盖章发生在DO里NEW lcl_express( 3 ) → 只是把「单号3」写在对象身上 run_inst → 把这个对象交给空闲工人 工人调用 DO → 等 2 秒写下「单号3已签收」 对象寄回来 → 你从 mv_msg 里读到这句话所以DO不需要IMPORTING单号已经在MV_BILL里了。run_inst这一行会卡住不是「分完件你就去干别的」。10 个件全回来或失败这行才结束。你是调度要在现场等。回来的LT_OUT是回执本一行一个件字段人话INST盖完寄回来的那个快递对象失败时可能是空的INDEX当初清单里第几个1 号、2 号…TIME这个工人盖这个件花了几秒MESSAGE有字 这个件出事了超时、程序 dumplo_one ? ls_out-inst回执上没写「这是快递」只写「一个会做 DO 的工人」。要转回LCL_EXPRESS才能看MV_MSG。失败时不要转——东西可能没寄回来。所以先看MESSAGE和INST IS NOT BOUND。3.2 用 SM50 确认「真的有几个人在盖」F8 之后立刻打开SM50。这 2 秒里应能看到多条对话进程同时亮程序名经常是RS_ABAP_PARALLEL_INST。那就是被借来的仓管。想看得更久把WAIT UP TO 2 SECONDS改成 5。看完改回去即可。4. 运行时到底发生了什么主程序所在的进程下文叫协调进程领班。被借来跑DO的进程叫工作进程厨师。你的报表调度站在柜台 │ │ NEW 出 10 个快递对象每个身上写着单号 │ ▼ RUN_INST 开始你站着等 │ ├── 把「单号1」递给工人 A ──► 盖章 2 秒 ──► 写上已签收 ──► 送回 ├── 把「单号2」递给工人 B ──► 盖章 2 秒 ──► ... └── 把「单号3」递给工人 C ──► ... 工人不够就排队盖完一件再拿下一件 │ ▼ 10 个回执都齐了 │ RUN_INST 结束你 WRITE 屏幕有三句话第一次学最容易忽略1. 工作进程是「新开的一次程序运行」。它会重新加载你的报表。选择屏幕上勾的钩、你在屏幕填的工厂不会自动出现在工作进程里。要带过去的东西必须在NEW任务对象时塞进属性。2. 两个进程的内表不是同一块内存。协调进程的GT_OUT和线程里的GT_OUT各是各的。线程里改了协调进程看不见除非写进任务对象属性随对象寄回来或线程里自己COMMIT写数据库协调进程再查表。3.EXPORT TO MEMORYABAP 内存跨不过去。那是当前会话的抽屉。工作进程是另一个会话抽屉是空的。共享数据请放对象属性或写数据库例如INDX。5. 从「一个快递」改成「一包物料」只改三处刚才一个任务 一个快递单号。真实批导里一个任务通常 一包内表比如 1000 条待扩展物料。还是盖章那套工人还是工人只是手里从「一个件」换成「一筐件」。把任务类想象成填空CLASS lcl_task DEFINITION. PUBLIC SECTION. INTERFACES if_abap_parallel. METHODS constructor IMPORTING it_data TYPE 你的内表类型 这一包要处理的数据 is_flag TYPE 你的开关结构. 选择屏幕勾了哪些视图也要带上 METHODS get_results EXPORTING et_data TYPE 你的内表类型. PRIVATE SECTION. DATA mt_data TYPE 你的内表类型. DATA ms_flag TYPE 你的开关结构. ENDCLASS. CLASS lcl_task IMPLEMENTATION. METHOD constructor. mt_data it_data. ms_flag is_flag. ENDMETHOD. METHOD if_abap_parallel~do. 下面三行是关键工作进程里全局变量是空的要自己灌 gt_out mt_data. p_accou ms_flag-p_accou. p_qm ms_flag-p_qm. PERFORM 原来的单条或单包处理. 里面调 BAPI、COMMIT 本包 mt_data gt_out. 处理结果带回来 ENDMETHOD. METHOD get_results. et_data mt_data. ENDMETHOD. ENDCLASS.主程序从「DO 10 TIMES」变成「按包 LOOP」 1. 先把全部待处理数据放进 gt_all和以前串行一样 2. 每 1000 条切一包NEW 一个任务 DATA lt_pack TYPE 你的内表类型. DATA lt_in TYPE cl_abap_parallelt_in_inst_tab. DATA ls_flag TYPE 你的开关结构. ls_flag-p_accou p_accou. 屏幕上的勾拷进结构 ls_flag-p_qm p_qm. LOOP AT gt_all INTO DATA(ls_row). APPEND ls_row TO lt_pack. IF lines( lt_pack ) 1000. APPEND NEW lcl_task( it_data lt_pack is_flag ls_flag ) TO lt_in. CLEAR lt_pack. ENDIF. ENDLOOP. IF lt_pack IS NOT INITIAL. APPEND NEW lcl_task( it_data lt_pack is_flag ls_flag ) TO lt_in. ENDIF. 3. 还是这一句 NEW cl_abap_parallel( p_percentage 50 )-run_inst( EXPORTING p_in_tab lt_in IMPORTING p_out_tab DATA(lt_out) ). 4. 收结果和快递例子一样先看失败再 ? 转回任务类原来的 BAPI FORM 可以不重写。并行框架只负责换厨房炒什么菜还是你原来那盘。6. 第一次实战只记这 6 条① 一包里的行不要抢同一把锁同一物料号 同一工厂不要同时出现在两个正在跑的包里。否则两个对话进程一起改同一条主数据会互等甚至超时。分包前按业务主键去重同一主键只进一包。② 选择屏幕的值必须放进任务对象工作进程看不到P_ACCOU、P_WERKS1。漏传的表现非常像 bug「我明明勾了会计视图扩出来利润中心是空的」。其实线程里那个勾是初始值。③ 每一包自己 COMMIT工作进程跑完对象就被收回。没提交的数据库更改会丢。BAPI 成功后在这一包里BAPI_TRANSACTION_COMMIT。包 A 成功、包 B 失败互不影响——这正是并行的好处。④ 包别切太碎也别切太大切法后果一条一个任务寄对象的时间可能比 BAPI 还长一包五万条一个对话进程占太久容易超时失败就整包重来一包几百一千条BAPI 类比较常见的起点⑤ 先顺序跑通再开并行RUN_INST有个P_DEBUG。传到X时不请厨师就在当前程序里一个一个调DO。这样你才能下断点。lo_par-run_inst( EXPORTING p_in_tab lt_in p_debug X 开发时打开测并行时去掉 IMPORTING p_out_tab lt_out ).⑥ 很短的活不要并行官方说得很直白把数据搬到另一个对话进程是要花时间的。赋值、拼字符串这种并行只会更慢。7. 对照物料扩展程序 ZMMU002S 是怎么套的看懂第 35 节之后这段只是「一个快递换成一包物料」。不必一次记完所有表名。7.1 它在并行什么从源工厂把物料主数据扩到目标工厂。一条大约要BAPI_MATERIAL_SAVEDATA必要时质量视图 BAPI必要时 MRP 区域函数一条大约 1 秒一次上万条所以值得并行。读 Excel 仍然在前台做要 SAP 窗口读完再并行。7.2 和最小例子的对应关系快递例子ZMMU002SLCL_EXPRESSLCL_MAT_PARALLEL写在同一个报表里的本地类MV_BILL一个单号MT_DATA这一筐待扩展物料没有开关MS_PARAM销售/采购/会计等勾选DO里等 2 秒再写「已签收」DO里灌全局变量再PERFORM MATERIEL_IMPORT10 个快递 10 个任务每 1000 条P_PKG 1 个任务RUN_INST同样P_PERCENTAGE 60屏幕WRITE签收结果从对象GET_RESULTS失败则按批次号更新日志表任务类的DO只有三步和填空模板一样METHOD if_abap_parallel~do. init_log_records( ). 准备本包日志内表 execute_import( ). 参数写回 P_* / GT_*再调原来的导入 save_log_final( ). MODIFY 日志表并 COMMIT ENDMETHOD.EXECUTE_IMPORT里把属性倒回报表全局变量——因为原来的 FORM 读的就是这些全局变量p_accou ms_param-p_accou. p_qm ms_param-p_qm. ... 其它勾选同样抄一遍 gt_tq34 mt_tq34. 质量类型协调进程查一次带进来 gt_mdma mt_mdma. MRP 区域是否已存在也是带进来 gt_out mt_data. PERFORM materiel_import USING gt_mdma. mt_data gt_out.7.3 分包用了批次号每条待处理数据带一个BATCHNO例如20260924-170000-0001。结构里BATCHNO必须是第一个字段才能用AT NEW batchno把同一包切出来AT NEW会看这个字段左边的所有字段。批次号还有第二个用处某个工作进程短 dump 了对象回不来协调进程仍可UPDATE zmmt146_log SET status E msg_mm ls_out-message WHERE batchno 这一包的批次号.把「整包任务挂了」和「某一条 BAPI 报错」分开记。7.4 只读表不要每个线程查一遍表谁查为什么传入TQ34程序开头查一次每个线程质量视图都要用MDMA开线程前按本批物料查一次已经有 MRP 区域就跳过创建这些内表作为任务属性被一起寄走。不要把全厂无关数据塞进每一个包。7.5 用户要关窗口时作业里面再并行RUN_INST会让当前这个进程一直等到全部包结束。用户如果直接把 GUI 关了领班死了厨师也会被收掉。所以勾了后台执行时前台只负责读 Excel把物料号写入数据库簇表INDXJOB_OPENSUBMIT 本程序 VIA JOB用户可以关窗后台作业当领班取回物料号、生成待处理内表、再RUN_INST作业是后台进程BTCRUN_INST仍然去借对话进程DIA。SM37 里看到的是作业总时间SM50 里才能看到几条 DIA 在真正跑 BAPI。8. 怎么判断「真的并行了」你看到的说明SM50 同时有多条RS_ABAP_PARALLEL_INST并行在干活SM50 长期只有 1 条几乎没并行百分比太低、DIA 不够、或P_DEBUG还开着SM37 显示 2 小时那是领班等待的墙钟时间不是「一个进程串行了 2 小时」50 个包、同时只有 5 个厨师总时间大约是「10 波 × 每包时间」不是 50 倍加速粗算总时间 ≈ 向上取整(包数 ÷ 同时在跑的DIA数) × 一包时间5 万条、每包 1000 条 → 50 包。同时 5 个 DIA、每包 3 分钟 → 大约 30 分钟而不是 3 分钟。若 SM50 只有 5 个 DIA 可借把百分比从 60 调到 80 也变不出更多厨师。9. 领班的两个常用旋钮第一次只用这两个DATA(lo_par) NEW cl_abap_parallel( p_percentage 50 最多占用「可借给并行的对话进程」的一半默认就是 50 p_timeout 0 0 不另设超时沿用系统限制 ). lo_par-run_inst( ... ).P_PERCENTAGE不是「占全部对话进程的 50%」。系统会先留一些对话进程给真正的前台用户防止别人登不进去剩下那部分才拿来并行。再在这上面乘百分比。生产上不要开到 100。物料扩展这种重活案例里用了 60已经略激进。P_TIMEOUT不要随手填 60。一包 1000 条 BAPI 可能跑很多分钟填 60 秒会把正常包全部判失败。其它参数只用本服务器、最多几个任务、一挂就停放在文末附录第一次可以不看。10. 你的系统够不够用这套写法在 ADT 或 SE24 打开类CL_ABAP_PARALLEL看有没有方法RUN_INST。看到什么怎么办有RUN_INST按本文写一般是 7.54 及以上S/4 常见只有RUN没有RUN_INST系统偏老。要用另一种「自己把数据打成 XSTRING」的写法见附录 C类都没有基线太老继续用传统 aRFC或让 Basis 看 Note 2791374 能否下发案例程序所在系统是SAP_BASIS 7.56有RUN_INST。11. 学完后动手的顺序把第 3 节快递程序跑通。十个件应大约 2 秒结束SM50 里能看到多个人同时盖章。打开P_DEBUG X对DO下断点确认能停住这时会变回一个一个盖大约 20 秒。把「一个单号」换成「你的内表的一小包」DO里调现有 FORM。确认这一包在调试模式下 BAPI COMMIT 日志都对。关掉P_DEBUGP_PERCENTAGE先 30 再 50。用 SM50 数实际有几个工人再决定一筐装多少条。不要一上来就把 5 万条、Excel、后台作业、日志表一起改。那是第 7 节的完整形态不是第一天的作业。附录 A构造参数全表需要调优再看7.56 系统里CL_ABAP_PARALLEL构造方法参数第一次要不要管含义P_PERCENTAGE要占用可借 DIA 的百分比默认 50P_TIMEOUT通常 0单个任务最多跑多少秒P_NUM_TASKS可选同时跑的任务上限。和百分比一起传时实际取更保守的那个P_LOCAL_SERVER一般不传X 只用当前应用服务器。任务若碰本机文件再打开P_NUM_PROCESSES不要用类注释写明已过时P_ABORT_ON_ERROR可选有任务因 RFC/短 dump 挂掉时剩下的任务不再进DO另外还有FORK/JOIN不必等全部结束就能收已完成的包、后台提交类方法。那是进阶第一次做批导用不到。RUN_INST是等到全部结束才返回。附录 B回来的结果行怎么判断LOOP AT lt_out INTO DATA(ls_done). IF ls_done-message IS NOT INITIAL OR ls_done-inst IS NOT BOUND. 任务级失败整包没正常跑完 用 ls_done-index 找回当初那一包去更新日志 CONTINUE. ENDIF. DATA(lo_task) CAST lcl_task( ls_done-inst ). lo_task-get_results( IMPORTING et_data DATA(lt_part) ). 业务级失败某条 BAPI 报 E已经写在 lt_part 或日志表里 ENDLOOP.两种失败不要混任务失败厨师把灶台烧了dump、超时。对象可能没了。业务失败厨师把菜做完了但这道菜盐放多了BAPI 返回 E。对象还在。附录 C没有 RUN_INST 时的另一条路了解即可更老的系统只有RUN自己把每包打成XSTRING任务类继承CL_ABAP_PARALLEL并重写DO在DO里再解开。样板代码明显更多。能用RUN_INST就不要走这条。社区博文Using class CL_ABAP_PARALLEL for mass parallel dialog work processesDan Mazurowski有完整RUN示例。附录 D参考事务 SE24 / ADT类CL_ABAP_PARALLEL上按 F2看 ABAP Doc比 Help 网页全SAP Help Portal 搜Parallel ProcessingCL_ABAP_PARALLELSAP Note 2791374类下发到 7.50、2880858RUN_INST从 7.54 起不下放到更低版本接口IF_ABAP_PARALLEL继承IF_SERIALIZABLE_OBJECT方法只有DOSM50 里的函数名RS_ABAP_PARALLEL_INST用来确认并行在跑不要当业务 API 去调结束前再重复一遍你真正要写的永远是这三块一个任务类属性 一包数据 配置DO 处理这一包。主程序切包NEW出 N 个对象。一句RUN_INST卡住直到全部结束再按成功/失败收卷。快递例子就是这三块。物料扩展也是这三块只是一筐更重、多传了选择屏幕和两张缓存表、多写了一张日志表。先把 10 个快递跑通再往筐里塞 BAPI。