异步 Apex:Future、Batch、Queueable 与 Scheduler

异步 Apex 让你在后台运行进程、获得更高 governor 限制、不阻塞用户。本文系统介绍异步处理原理,以及 Future Methods、Batch Apex、Queueable Apex、Scheduled Apex 四种异步方式的语法、适用场景、测试方法和最佳实践,最后讲解如何监控异步作业。...

📅 2026/9/26 ✍️ ponybai 🏷️ salesforce, apex, developer, headless

学习目标

slide_2

完成本单元后,你将能够:

  • 解释同步与异步处理的区别。
  • 在不同场景中选择合适的异步 Apex 类型。

异步 Apex

slide_3

异步 Apex 用于在单独的线程中、稍后运行进程——异步进程在「后台」执行任务,用户无需等待任务完成。一个现实类比:如果你把车送到修理厂并一直等到修好再办其他事,这是同步处理;如果你把车留在修理厂、先去办其他事、等修理厂打电话,这就是异步处理——异步让你在同样时间内完成更多事。

异步 Apex 适合:外部系统 callout、需要更高限制的操作、需要在特定时间运行的代码。关键好处包括:用户效率(用户继续工作、处理在后台执行)、可扩展性(平台可并行处理更多作业)、更高限制(异步进程以更高 governor 限制在新线程中启动)。

异步 Apex 类型概览:Future Methods(在资源可用时运行,适合 Web 服务 callout)、Batch Apex(运行超出正常限制的大作业,适合数据清理或归档)、Queueable Apex(类似 future 但支持作业链和复杂数据类型)、Scheduled Apex(在指定时间运行,适合每日/每周任务)。这些类型并非互斥,例如常见模式是从 scheduled 作业启动 batch 作业。

更高的 Governor 与执行限制

slide_4

运行异步 Apex 的主要好处之一是更高的 governor 和执行限制。例如异步调用时 SOQL 查询数从 100 翻倍到 200,总堆大小和最大 CPU 时间也更大。更重要的是,这些 governor 限制独立于最初排队异步请求的同步请求的限制——本质上是两个独立的 Apex 调用,处理能力翻倍以上。这在你想在当前事务中尽可能多处理、但接近 governor 限制时继续异步处理的情况下非常有用。

异步处理如何运作

slide_5

多租户环境中的异步处理面临额外挑战:确保处理公平(每个客户公平获得资源)、确保容错(异步请求不因设备或软件故障丢失)。平台使用基于队列的异步处理框架,请求生命周期分三部分:入队(请求放入队列,附带处理所需数据)、持久化(请求存储到持久存储用于故障恢复和事务能力)、出队(请求从队列移除并处理;处理失败时事务控制确保请求不丢失)。每个请求由 handler 处理,handler 由每个应用服务器上有限数量的 worker 线程执行。

资源保护

slide_6

异步处理的优先级低于浏览器和 API 的实时交互。为确保有足够资源应对计算资源增长,队列框架监控服务器内存和 CPU 使用等系统资源,超过阈值时减少异步处理——这就是多租户系统的自我保护。如果某个 org 试图使用超过其份额的资源,该 org 的异步处理会被暂停,直到回到正常阈值。处理时间没有保证,但最终都会完成。

使用 Future 方法

slide_7

本单元介绍 @Future 注解方法:何时使用、限制、如何用于 callout,以及最佳实践。

学习目标

slide_8

完成本单元后,你将了解:何时使用 future 方法、使用 future 方法的限制、如何用 future 方法做 callout,以及 future 方法的最佳实践。

Future Apex

slide_9

带 @Future 注解的方法(简称 future 方法)在系统资源可用时、在单独的线程中异步运行。一般推荐使用 queueable Apex 而非 future 方法——queueable 有相同用例但额外提供作业 ID、非原始类型支持和作业链。future 方法通常用于:外部 Web 服务 callout(从触发器或 DML 后发起 callout 必须用 future 方法或 Queueable 接口)、想在独立线程运行的操作(如资源密集型计算或记录处理)、隔离不同 sObject 类型的 DML 操作以避免混合 DML 错误。

Future 方法语法

slide_10

Future 方法必须是静态方法、只能返回 void 类型。指定参数必须是原始数据类型或原始数据类型的集合——future 方法不能把标准或自定义对象作为参数。常见模式是把要异步处理的记录 ID 列表传给方法。对象不能作为参数传入的原因:调用方法与实际执行之间对象可能已变化(future 方法在资源可用时才执行,可能拿到旧值)。另外,future 方法不保证按调用顺序执行;两个 future 方法可能并发运行,若更新同一条记录可能导致记录锁定。

示例 Callout 代码

slide_11

要对外部服务或 API 发起 Web 服务 callout,创建一个带 (callout=true) 注解的 future 方法。示例类同时提供同步和异步两种 callout 方法,同步调用还会向自定义日志对象插入记录以跟踪 callout 状态。

测试类

slide_12

测试 future 方法与常规 Apex 测试略有不同:把测试代码放在 startTest() 和 stopTest() 之间。系统会收集 startTest() 之后发出的所有异步调用,执行 stopTest() 时这些异步进程会同步运行,然后就可以断言异步调用是否正确执行。注意:测试代码不能真正向外部系统发送 callout,需要「mock」callout 来获得测试覆盖率。

最佳实践

slide_13

由于每个 future 方法调用都会向异步队列添加一个请求,要避免短时间内添加大量 future 请求的设计模式——如果设计可能在一次添加 2000 个以上请求,请求可能因流量控制而延迟。最佳实践:确保 future 方法尽快执行;Web 服务 callout 尽量在同一个 future 方法中捆绑所有 callout;大规模彻底测试(测试触发器能否处理 200 条记录的触发器集合);处理大量记录时考虑用 batch Apex 而非为每条记录创建 future 请求。

注意事项

slide_14

使用 future 方法要记住:必须是静态方法、只能返回 void;参数只能是原始类型或原始类型集合(不能传对象);不保证按调用顺序执行、可能并发运行导致记录锁定;不能在 Visualforce 控制器的 getMethodName/setMethodName 或构造函数中使用;不能从 future 方法调用 future 方法(用 System.isFuture() 防止递归);getContent() 和 getContentAsPDF() 不能在 @Future 方法中使用;每次 Apex 调用最多 50 个 future 调用,且 24 小时内有额外限制。

使用 Batch Apex

slide_15

本单元介绍 batch Apex:何时使用、语法、状态管理、测试和最佳实践。

学习目标

slide_16

完成本单元后,你将了解:batch Apex 的使用场景、更高的 Apex 限制、batch Apex 语法和最佳实践。

Batch Apex

slide_17

Batch Apex 用于运行会超出正常处理限制的大作业(成千上万甚至数百万条记录)。用 batch Apex 可以异步分批处理记录以保持在平台限制内。处理大量记录(如数据清理或归档)时,batch Apex 可能是最佳选择。batch 类的执行逻辑对每批记录调用一次,每次调用 batch 类时作业放入 Apex 作业队列、作为独立事务执行。两大优势:每个事务以全新的 governor 限制开始;若某批处理失败,其他成功的批事务不会回滚。

Batch Apex 语法

slide_18

要编写 batch Apex 类,类必须实现 Database.Batchable 接口,并包含三个方法:start()、execute()、finish()。

start()

slide_19

start() 方法用于收集要传给 execute() 方法处理的记录或对象。它在 batch 作业开始时调用一次,返回 Database.QueryLocator 对象或包含传给作业的记录/对象的 Iterable。多数情况下,用简单的 SOQL 查询生成 QueryLocator 即可;QueryLocator 能绕过 SOQL 检索记录总数的 governor 限制(最多可查询 5000 万条记录),而 Iterable 仍受该限制约束。

execute()

slide_20

execute() 方法对传入的每一「批」数据执行实际处理。默认批大小为 200 条记录,各批不保证按 start() 方法返回的顺序执行。execute() 接收两个参数:Database.BatchableContext 对象的引用,以及 sObject 列表(如 List<sObject>)或参数化类型列表。

finish()

slide_21

finish() 方法用于执行后处理操作(例如发送邮件),在所有批处理完成后调用一次。

调用 Batch 类

slide_22

调用 batch 类只需实例化它,然后调用 Database.executeBatch(instance);可选传入第二个 scope 参数指定每批传给 execute 方法的记录数(遇到 governor 限制时建议调小批大小)。每次 batch Apex 调用会创建一条 AsyncApexJob 记录,可通过 SOQL 跟踪进度或在 Apex 作业队列中管理。

Batch Apex 中的状态

slide_23

Batch Apex 通常是无状态的——每次执行都视为独立事务(例如含 1000 条记录、默认批大小的作业视为 5 个各 200 条的事务)。若在类定义中指定 Database.Stateful,可在所有事务间维护状态;只有实例成员变量会跨事务保留值,这对计数或汇总处理中的记录很有用。

Batch Apex 代码示例

slide_24

示例业务需求:美国公司账户的所有联系人必须以父公司的账单地址作为邮寄地址。batch 类的 start() 用 QueryLocator 查询 Billing Country 为 'USA' 的所有账户;execute() 把每个联系人的邮寄地址设为账户账单地址、并累加已处理记录数;finish() 查询 AsyncApexJob 获取作业状态和提交者邮箱,向提交者发送包含作业信息和更新联系人数量的通知邮件(用 Database.Stateful 跟踪状态)。

测试 Batch Apex

slide_25

测试 batch 类:插入测试记录、调用 batch 类、断言记录被正确更新。用 @TestSetup 注解的 setup() 方法创建公共测试记录(例如插入 10 个美国账户及关联联系人)。注意插入的记录数要 ≤ 200(批大小),因为测试方法只能执行一批。test() 方法在 Test.startTest/Test.stopTest 块内调用 Database.executeBatch,作业在 Test.stopTest 之后同步执行,最后断言所有联系人记录已正确更新。

最佳实践

slide_26

使用 batch Apex 的最佳实践:只有当记录超过一批时才用 batch Apex(否则用 queueable Apex);调优 SOQL 查询尽快收集记录;最小化创建的异步请求数以降低延迟风险;从触发器调用 batch 作业要格外小心,必须保证触发器添加的 batch 作业不超过限制。

用 Queueable Apex 控制流程

slide_27

本单元介绍 Queueable 接口:何时使用、与 future 方法的区别、语法、作业链和最佳实践。

学习目标

slide_28

完成本单元后,你将了解:何时使用 Queueable 接口、queueable 与 future 方法的区别、Queueable Apex 语法和最佳实践。

Queueable Apex

slide_29

Queueable Apex 本质上是 future 方法的超集,额外好处包括:非原始类型(类可包含 sObject 或自定义 Apex 类型等非原始数据类型的成员变量)、监控(System.enqueueJob 返回 AsyncApexJob 记录 ID,可用来识别和监控作业)、链式作业(可从运行中的作业启动第二个作业,用于需要顺序处理的情况)。

Queueable vs Future

slide_30

queueable 方法与 future 方法功能等价,多数时候应优先用 queueable。但也不必急着重构所有 future 方法。当你的功能有时同步执行、有时异步执行时,用 future 方法更容易——只需创建一个包装同步方法的 future 方法。

Queueable 语法

slide_31

使用 queueable Apex,只需实现 Queueable 接口。

示例代码

slide_32

常见的 queueable 工作流是获取一组 sObject 记录、处理后在数据库中异步更新。因为 future 方法限制为原始数据类型,queueable 是理想选择。示例代码接收账户记录集合,为每条设置 parentId,然后更新数据库中的记录。提交后作业加入队列,在系统资源可用时处理,可用作业 ID 监控进度。

测试 Queueable Apex

slide_33

测试 queueable 作业与 batch Apex 类似:用 @TestSetup 创建测试记录(如一个父账户和 100 个子账户),在 Test.startTest/Test.stopTest 块内提交作业,系统在 Test.stopTest 后同步执行,然后查询账户记录验证作业结果。

链式作业

slide_34

Queueable Apex 最棒的特性之一是作业链。要链接作业,从 queueable 类的 execute() 方法中提交第二个作业。一个执行中的作业只能添加一个作业(每个父作业只能有一个子作业)。测试链式作业可用定义的栈深度,但要注意适用的 Apex governor 限制。

注意事项

slide_35

使用 queueable Apex 注意:排队作业的执行计入异步 Apex 方法执行的共享限制;单次事务最多用 System.enqueueJob 添加 50 个作业;链式作业时一个执行中的作业只能添加一个子作业;链式作业深度无限制,但 Developer Edition 和 Trial org 最大栈深度为 5(可链接 4 次,含初始父作业共 5 个),可通过配置覆盖。

用 Apex Scheduler 调度作业

slide_36

本单元介绍 scheduled Apex:何时使用、语法、调度方式和最佳实践。

学习目标

slide_37

完成本单元后,你将了解:何时使用 scheduled Apex、如何监控调度作业、Scheduled Apex 语法和最佳实践。

Scheduled Apex

slide_38

Apex Scheduler 让你能延迟执行,在指定时间运行 Apex 类,非常适合每日或每周的维护任务(用 batch Apex)。要使用调度器,编写实现 Schedulable 接口的 Apex 类,然后按特定计划调度执行。

Scheduled Apex 语法

slide_39

要让 Apex 类在特定时间运行,先让类实现 Schedulable 接口(必须实现 execute() 方法,参数是 SchedulableContext 对象),然后用 System.schedule() 方法调度实例。调度后创建代表调度作业的 CronTrigger 对象,提供 getTriggerId() 方法返回 CronTrigger API 对象 ID。

示例代码

slide_40

示例类查询本应在当前日期前关闭的未关闭商机,为每个商机创建任务提醒所有者更新商机。可以程序化调度,也可以从 Apex Scheduler UI 调度。

使用 System.Schedule 方法

slide_41

实现 Schedulable 接口后,用 System.schedule() 方法执行。它基于用户时区,接收三个参数:作业名称、表示调度运行日期时间的 CRON 表达式、实现 Schedulable 接口的类实例。从触发器调度类要格外小心,必须保证触发器添加的调度作业不超过限制(考虑 API 批量更新、导入向导、UI 批量改记录等情况)。

从 UI 调度作业

slide_42

也可以从 UI 调度:在 Setup 的 Quick Find 中输入 Jobs,选择 Scheduled Jobs,点击 Schedule Apex,填写作业名(如 Daily Oppty Reminder),选择 Apex 类(搜索 * 列出所有可调度类),选频率(每周/每月)、起止日期和开始时间,保存。

测试 Scheduled Apex

slide_43

与其他异步方法一样,测试 scheduled Apex 时要用 startTest()/stopTest() 包裹 System.schedule() 方法,确保调度作业在继续测试前完成。

注意事项

slide_44

Scheduled Apex 注意事项:一次最多 100 个 scheduled Apex 作业,24 小时内调度执行次数有上限;从触发器调度要格外小心;scheduled Apex 不支持同步 Web 服务 callout(要做 callout,放在 @Future(callout=true) 方法中从 scheduled Apex 调用;若 scheduled Apex 执行 batch 作业,则 batch 类支持 callout)。

监控异步 Apex

slide_45

本单元介绍如何监控不同类型的异步作业,以及如何使用 Flex Queue。

学习目标

slide_46

完成本单元后,你将了解:如何监控不同类型的作业,以及如何使用 Flex Queue。

监控异步作业

slide_47

异步作业在后台静默工作,需要一些方法监控其状态。可以在 Salesforce UI 中监控所有作业:从 Setup 的 Quick Find 输入 Jobs,选择 Apex Jobs。Apex Jobs 页面显示所有异步 Apex 作业及执行信息。如果有大量 batch 作业,用 Batch Jobs 页面只查看 batch 作业(点击 Apex Jobs 页面顶部的链接打开),可用滑块选择日期范围;点击某类 ID 旁的 More Info 查看该类作业详情。也可以在 Apex Flex Queue 中监控和重排作业顺序,控制先处理哪些作业。

监控 Future 作业

slide_48

Future 作业像其他作业一样出现在 Apex Jobs 页面,但目前不属于 Flex Queue。可以查询 AsyncApexJob 查找 future 作业,但有个注意事项:启动 future 作业不返回 ID,所以要按 MethodName 或 JobType 等其他字段过滤查找。

用 SOQL 监控队列作业

slide_49

要查询已提交作业的信息,对 AsyncApexJob 执行 SOQL 查询,按 System.enqueueJob() 返回的作业 ID 过滤。

用 Flex Queue 监控队列作业

slide_50

Apex Flex Queue 允许你提交最多 100 个 batch 作业执行,提交的作业处于 holding 状态并放入 Flex Queue。作业按先进先出处理,你可以查看当前队列顺序并重排,把重要作业移到前面。系统资源可用时,从 Flex Queue 顶部取下一个作业移到 batch 作业队列(状态从 Holding 变为 Queued),每个 org 最多同时处理 5 个排队或活跃作业。

监控调度作业

slide_51

Apex 作业调度后,可对 CronTrigger 执行 SOQL 查询获取更多信息(如作业运行次数、下次调度时间)。若在 schedulable 类的 execute 方法内查询,可通过 SchedulableContext 参数的 getTriggerId() 获取当前作业 ID。还可以通过 CronJobDetail 关联获取作业名称和类型;要获取所有 scheduled Apex 作业总数,可查询 CronJobDetail(job type 为 7 对应 scheduled Apex)。


文章来源:Trailhead - Asynchronous Apex