学习目标
完成本单元后,你将能够:
- 解释同步与异步处理的区别。
- 在不同场景中选择合适的异步 Apex 类型。
异步 Apex
异步 Apex 用于在单独的线程中、稍后运行进程——异步进程在「后台」执行任务,用户无需等待任务完成。一个现实类比:如果你把车送到修理厂并一直等到修好再办其他事,这是同步处理;如果你把车留在修理厂、先去办其他事、等修理厂打电话,这就是异步处理——异步让你在同样时间内完成更多事。
异步 Apex 适合:外部系统 callout、需要更高限制的操作、需要在特定时间运行的代码。关键好处包括:用户效率(用户继续工作、处理在后台执行)、可扩展性(平台可并行处理更多作业)、更高限制(异步进程以更高 governor 限制在新线程中启动)。
异步 Apex 类型概览:Future Methods(在资源可用时运行,适合 Web 服务 callout)、Batch Apex(运行超出正常限制的大作业,适合数据清理或归档)、Queueable Apex(类似 future 但支持作业链和复杂数据类型)、Scheduled Apex(在指定时间运行,适合每日/每周任务)。这些类型并非互斥,例如常见模式是从 scheduled 作业启动 batch 作业。
更高的 Governor 与执行限制
运行异步 Apex 的主要好处之一是更高的 governor 和执行限制。例如异步调用时 SOQL 查询数从 100 翻倍到 200,总堆大小和最大 CPU 时间也更大。更重要的是,这些 governor 限制独立于最初排队异步请求的同步请求的限制——本质上是两个独立的 Apex 调用,处理能力翻倍以上。这在你想在当前事务中尽可能多处理、但接近 governor 限制时继续异步处理的情况下非常有用。
异步处理如何运作
多租户环境中的异步处理面临额外挑战:确保处理公平(每个客户公平获得资源)、确保容错(异步请求不因设备或软件故障丢失)。平台使用基于队列的异步处理框架,请求生命周期分三部分:入队(请求放入队列,附带处理所需数据)、持久化(请求存储到持久存储用于故障恢复和事务能力)、出队(请求从队列移除并处理;处理失败时事务控制确保请求不丢失)。每个请求由 handler 处理,handler 由每个应用服务器上有限数量的 worker 线程执行。
资源保护
异步处理的优先级低于浏览器和 API 的实时交互。为确保有足够资源应对计算资源增长,队列框架监控服务器内存和 CPU 使用等系统资源,超过阈值时减少异步处理——这就是多租户系统的自我保护。如果某个 org 试图使用超过其份额的资源,该 org 的异步处理会被暂停,直到回到正常阈值。处理时间没有保证,但最终都会完成。
使用 Future 方法
本单元介绍 @Future 注解方法:何时使用、限制、如何用于 callout,以及最佳实践。
学习目标
完成本单元后,你将了解:何时使用 future 方法、使用 future 方法的限制、如何用 future 方法做 callout,以及 future 方法的最佳实践。
Future Apex
带 @Future 注解的方法(简称 future 方法)在系统资源可用时、在单独的线程中异步运行。一般推荐使用 queueable Apex 而非 future 方法——queueable 有相同用例但额外提供作业 ID、非原始类型支持和作业链。future 方法通常用于:外部 Web 服务 callout(从触发器或 DML 后发起 callout 必须用 future 方法或 Queueable 接口)、想在独立线程运行的操作(如资源密集型计算或记录处理)、隔离不同 sObject 类型的 DML 操作以避免混合 DML 错误。
Future 方法语法
Future 方法必须是静态方法、只能返回 void 类型。指定参数必须是原始数据类型或原始数据类型的集合——future 方法不能把标准或自定义对象作为参数。常见模式是把要异步处理的记录 ID 列表传给方法。对象不能作为参数传入的原因:调用方法与实际执行之间对象可能已变化(future 方法在资源可用时才执行,可能拿到旧值)。另外,future 方法不保证按调用顺序执行;两个 future 方法可能并发运行,若更新同一条记录可能导致记录锁定。
示例 Callout 代码
要对外部服务或 API 发起 Web 服务 callout,创建一个带 (callout=true) 注解的 future 方法。示例类同时提供同步和异步两种 callout 方法,同步调用还会向自定义日志对象插入记录以跟踪 callout 状态。
测试类
测试 future 方法与常规 Apex 测试略有不同:把测试代码放在 startTest() 和 stopTest() 之间。系统会收集 startTest() 之后发出的所有异步调用,执行 stopTest() 时这些异步进程会同步运行,然后就可以断言异步调用是否正确执行。注意:测试代码不能真正向外部系统发送 callout,需要「mock」callout 来获得测试覆盖率。
最佳实践
由于每个 future 方法调用都会向异步队列添加一个请求,要避免短时间内添加大量 future 请求的设计模式——如果设计可能在一次添加 2000 个以上请求,请求可能因流量控制而延迟。最佳实践:确保 future 方法尽快执行;Web 服务 callout 尽量在同一个 future 方法中捆绑所有 callout;大规模彻底测试(测试触发器能否处理 200 条记录的触发器集合);处理大量记录时考虑用 batch Apex 而非为每条记录创建 future 请求。
注意事项
使用 future 方法要记住:必须是静态方法、只能返回 void;参数只能是原始类型或原始类型集合(不能传对象);不保证按调用顺序执行、可能并发运行导致记录锁定;不能在 Visualforce 控制器的 getMethodName/setMethodName 或构造函数中使用;不能从 future 方法调用 future 方法(用 System.isFuture() 防止递归);getContent() 和 getContentAsPDF() 不能在 @Future 方法中使用;每次 Apex 调用最多 50 个 future 调用,且 24 小时内有额外限制。
使用 Batch Apex
本单元介绍 batch Apex:何时使用、语法、状态管理、测试和最佳实践。
学习目标
完成本单元后,你将了解:batch Apex 的使用场景、更高的 Apex 限制、batch Apex 语法和最佳实践。
Batch Apex
Batch Apex 用于运行会超出正常处理限制的大作业(成千上万甚至数百万条记录)。用 batch Apex 可以异步分批处理记录以保持在平台限制内。处理大量记录(如数据清理或归档)时,batch Apex 可能是最佳选择。batch 类的执行逻辑对每批记录调用一次,每次调用 batch 类时作业放入 Apex 作业队列、作为独立事务执行。两大优势:每个事务以全新的 governor 限制开始;若某批处理失败,其他成功的批事务不会回滚。
Batch Apex 语法
要编写 batch Apex 类,类必须实现 Database.Batchable 接口,并包含三个方法:start()、execute()、finish()。
start()
start() 方法用于收集要传给 execute() 方法处理的记录或对象。它在 batch 作业开始时调用一次,返回 Database.QueryLocator 对象或包含传给作业的记录/对象的 Iterable。多数情况下,用简单的 SOQL 查询生成 QueryLocator 即可;QueryLocator 能绕过 SOQL 检索记录总数的 governor 限制(最多可查询 5000 万条记录),而 Iterable 仍受该限制约束。
execute()
execute() 方法对传入的每一「批」数据执行实际处理。默认批大小为 200 条记录,各批不保证按 start() 方法返回的顺序执行。execute() 接收两个参数:Database.BatchableContext 对象的引用,以及 sObject 列表(如 List<sObject>)或参数化类型列表。
finish()
finish() 方法用于执行后处理操作(例如发送邮件),在所有批处理完成后调用一次。
调用 Batch 类
调用 batch 类只需实例化它,然后调用 Database.executeBatch(instance);可选传入第二个 scope 参数指定每批传给 execute 方法的记录数(遇到 governor 限制时建议调小批大小)。每次 batch Apex 调用会创建一条 AsyncApexJob 记录,可通过 SOQL 跟踪进度或在 Apex 作业队列中管理。
Batch Apex 中的状态
Batch Apex 通常是无状态的——每次执行都视为独立事务(例如含 1000 条记录、默认批大小的作业视为 5 个各 200 条的事务)。若在类定义中指定 Database.Stateful,可在所有事务间维护状态;只有实例成员变量会跨事务保留值,这对计数或汇总处理中的记录很有用。
Batch Apex 代码示例
示例业务需求:美国公司账户的所有联系人必须以父公司的账单地址作为邮寄地址。batch 类的 start() 用 QueryLocator 查询 Billing Country 为 'USA' 的所有账户;execute() 把每个联系人的邮寄地址设为账户账单地址、并累加已处理记录数;finish() 查询 AsyncApexJob 获取作业状态和提交者邮箱,向提交者发送包含作业信息和更新联系人数量的通知邮件(用 Database.Stateful 跟踪状态)。
测试 Batch Apex
测试 batch 类:插入测试记录、调用 batch 类、断言记录被正确更新。用 @TestSetup 注解的 setup() 方法创建公共测试记录(例如插入 10 个美国账户及关联联系人)。注意插入的记录数要 ≤ 200(批大小),因为测试方法只能执行一批。test() 方法在 Test.startTest/Test.stopTest 块内调用 Database.executeBatch,作业在 Test.stopTest 之后同步执行,最后断言所有联系人记录已正确更新。
最佳实践
使用 batch Apex 的最佳实践:只有当记录超过一批时才用 batch Apex(否则用 queueable Apex);调优 SOQL 查询尽快收集记录;最小化创建的异步请求数以降低延迟风险;从触发器调用 batch 作业要格外小心,必须保证触发器添加的 batch 作业不超过限制。
用 Queueable Apex 控制流程
本单元介绍 Queueable 接口:何时使用、与 future 方法的区别、语法、作业链和最佳实践。
学习目标
完成本单元后,你将了解:何时使用 Queueable 接口、queueable 与 future 方法的区别、Queueable Apex 语法和最佳实践。
Queueable Apex
Queueable Apex 本质上是 future 方法的超集,额外好处包括:非原始类型(类可包含 sObject 或自定义 Apex 类型等非原始数据类型的成员变量)、监控(System.enqueueJob 返回 AsyncApexJob 记录 ID,可用来识别和监控作业)、链式作业(可从运行中的作业启动第二个作业,用于需要顺序处理的情况)。
Queueable vs Future
queueable 方法与 future 方法功能等价,多数时候应优先用 queueable。但也不必急着重构所有 future 方法。当你的功能有时同步执行、有时异步执行时,用 future 方法更容易——只需创建一个包装同步方法的 future 方法。
Queueable 语法
使用 queueable Apex,只需实现 Queueable 接口。
示例代码
常见的 queueable 工作流是获取一组 sObject 记录、处理后在数据库中异步更新。因为 future 方法限制为原始数据类型,queueable 是理想选择。示例代码接收账户记录集合,为每条设置 parentId,然后更新数据库中的记录。提交后作业加入队列,在系统资源可用时处理,可用作业 ID 监控进度。
测试 Queueable Apex
测试 queueable 作业与 batch Apex 类似:用 @TestSetup 创建测试记录(如一个父账户和 100 个子账户),在 Test.startTest/Test.stopTest 块内提交作业,系统在 Test.stopTest 后同步执行,然后查询账户记录验证作业结果。
链式作业
Queueable Apex 最棒的特性之一是作业链。要链接作业,从 queueable 类的 execute() 方法中提交第二个作业。一个执行中的作业只能添加一个作业(每个父作业只能有一个子作业)。测试链式作业可用定义的栈深度,但要注意适用的 Apex governor 限制。
注意事项
使用 queueable Apex 注意:排队作业的执行计入异步 Apex 方法执行的共享限制;单次事务最多用 System.enqueueJob 添加 50 个作业;链式作业时一个执行中的作业只能添加一个子作业;链式作业深度无限制,但 Developer Edition 和 Trial org 最大栈深度为 5(可链接 4 次,含初始父作业共 5 个),可通过配置覆盖。
用 Apex Scheduler 调度作业
本单元介绍 scheduled Apex:何时使用、语法、调度方式和最佳实践。
学习目标
完成本单元后,你将了解:何时使用 scheduled Apex、如何监控调度作业、Scheduled Apex 语法和最佳实践。
Scheduled Apex
Apex Scheduler 让你能延迟执行,在指定时间运行 Apex 类,非常适合每日或每周的维护任务(用 batch Apex)。要使用调度器,编写实现 Schedulable 接口的 Apex 类,然后按特定计划调度执行。
Scheduled Apex 语法
要让 Apex 类在特定时间运行,先让类实现 Schedulable 接口(必须实现 execute() 方法,参数是 SchedulableContext 对象),然后用 System.schedule() 方法调度实例。调度后创建代表调度作业的 CronTrigger 对象,提供 getTriggerId() 方法返回 CronTrigger API 对象 ID。
示例代码
示例类查询本应在当前日期前关闭的未关闭商机,为每个商机创建任务提醒所有者更新商机。可以程序化调度,也可以从 Apex Scheduler UI 调度。
使用 System.Schedule 方法
实现 Schedulable 接口后,用 System.schedule() 方法执行。它基于用户时区,接收三个参数:作业名称、表示调度运行日期时间的 CRON 表达式、实现 Schedulable 接口的类实例。从触发器调度类要格外小心,必须保证触发器添加的调度作业不超过限制(考虑 API 批量更新、导入向导、UI 批量改记录等情况)。
从 UI 调度作业
也可以从 UI 调度:在 Setup 的 Quick Find 中输入 Jobs,选择 Scheduled Jobs,点击 Schedule Apex,填写作业名(如 Daily Oppty Reminder),选择 Apex 类(搜索 * 列出所有可调度类),选频率(每周/每月)、起止日期和开始时间,保存。
测试 Scheduled Apex
与其他异步方法一样,测试 scheduled Apex 时要用 startTest()/stopTest() 包裹 System.schedule() 方法,确保调度作业在继续测试前完成。
注意事项
Scheduled Apex 注意事项:一次最多 100 个 scheduled Apex 作业,24 小时内调度执行次数有上限;从触发器调度要格外小心;scheduled Apex 不支持同步 Web 服务 callout(要做 callout,放在 @Future(callout=true) 方法中从 scheduled Apex 调用;若 scheduled Apex 执行 batch 作业,则 batch 类支持 callout)。
监控异步 Apex
本单元介绍如何监控不同类型的异步作业,以及如何使用 Flex Queue。
学习目标
完成本单元后,你将了解:如何监控不同类型的作业,以及如何使用 Flex Queue。
监控异步作业
异步作业在后台静默工作,需要一些方法监控其状态。可以在 Salesforce UI 中监控所有作业:从 Setup 的 Quick Find 输入 Jobs,选择 Apex Jobs。Apex Jobs 页面显示所有异步 Apex 作业及执行信息。如果有大量 batch 作业,用 Batch Jobs 页面只查看 batch 作业(点击 Apex Jobs 页面顶部的链接打开),可用滑块选择日期范围;点击某类 ID 旁的 More Info 查看该类作业详情。也可以在 Apex Flex Queue 中监控和重排作业顺序,控制先处理哪些作业。
监控 Future 作业
Future 作业像其他作业一样出现在 Apex Jobs 页面,但目前不属于 Flex Queue。可以查询 AsyncApexJob 查找 future 作业,但有个注意事项:启动 future 作业不返回 ID,所以要按 MethodName 或 JobType 等其他字段过滤查找。
用 SOQL 监控队列作业
要查询已提交作业的信息,对 AsyncApexJob 执行 SOQL 查询,按 System.enqueueJob() 返回的作业 ID 过滤。
用 Flex Queue 监控队列作业
Apex Flex Queue 允许你提交最多 100 个 batch 作业执行,提交的作业处于 holding 状态并放入 Flex Queue。作业按先进先出处理,你可以查看当前队列顺序并重排,把重要作业移到前面。系统资源可用时,从 Flex Queue 顶部取下一个作业移到 batch 作业队列(状态从 Holding 变为 Queued),每个 org 最多同时处理 5 个排队或活跃作业。
监控调度作业
Apex 作业调度后,可对 CronTrigger 执行 SOQL 查询获取更多信息(如作业运行次数、下次调度时间)。若在 schedulable 类的 execute 方法内查询,可通过 SchedulableContext 参数的 getTriggerId() 获取当前作业 ID。还可以通过 CronJobDetail 关联获取作业名称和类型;要获取所有 scheduled Apex 作业总数,可查询 CronJobDetail(job type 为 7 对应 scheduled Apex)。

















































