异步Apex类

一个Apex类可以定义为异步类,用于异步执行。

异步类可以通过多种方式实现:

  • Future注解
  • 批处理
  • Queueable接口
  • Schedulable接口

Future注解

使用Future注解可以将一个Apex函数定义为异步执行类。该类会拥有自己的线程,并在此线程中独立运行,实现异步效果。

Future注解的应用示例:

global class ExampleClass {
@future
public static void exampleFutureFunction(List<Id> recordIds) {
// ..
}
}

要注意的是,在Future函数中不可以用sObject对象作为参数,因为Future函数是独立运行的,如果将sObject对象传入其中,该sObject对象在Future函数运行的同时有可能被改变,这样有可能会造成错误。

定义为Future的函数必须是静态的(static),并且返回类型必须是void。

由于异步函数的特点,在多个Future函数同时执行的时候,执行的顺序是不确定的,完成的顺序也不确定。所以作为开发者,我们不能设想用一个Future函数的执行结果来影响另一个Future函数,也不能从Future函数中调用另一个Future函数。

在Apex函数中调用外部网络服务

在Apex函数中调用外部网络服务时,可以定义该函数为Future,并加入“callout=true”。比如:

@future(callout=true)
public static void callWebService() {
String result = ExampleWebServiceClass.getWebServiceResult();
}

通过这种方式,此函数不需要等待网络服务的回应,从而可以继续执行其他的功能。

Future函数单元测试

在对Future函数进行单元测试时,必须将测试的代码放入“startTest()”和“stopTest()”函数中间。“stopTest()”函数可以确保在异步执行的函数得到结果之后再继续执行后面的代码。比如:

@isTest
static void testExampleFutureFunction() {
// ... Test.startTest(); ExampleClass.exampleFutureFunction(); Test.stopTest(); // ...
}

最佳实践

由于Salesforce将所有异步函数保存在一个队列中执行,所以如果同时运行的Future函数过多,会导致异步执行的效率降低。

使用Future函数时,尽量保证该函数被尽快执行。

如果需要同时调用若干外部网络服务请求,尽量将这些请求放在一个Future函数中,而不要对每一个请求建立单独的Future函数。

批处理Apex

当开发者想在代码中处理大批量的数据时,很容易会使数据库查询的次数达到上限。为了避免这种情况,Apex提供了“Database.Batchable”接口,可以让开发者实现批处理Apex类,用于异步执行大批量数据的查询操作。

“Database.Batchable”接口包含了如下函数:

  • start():初始化函数,对要处理的数据进行初始化,并将要处理的数据作为返回值,分批传入execute()函数。返回的类型可以是“Database.QueryLocator”或“Iterable”
  • execute():从start()函数中分批接收数据,并对每批数据进行处理。数据被处理的顺序并不一定和从start()函数传入的顺序一致
  • finish():当execute()函数执行完成后,对数据进行最后的处理

一个标准的批处理类的结构:

global class ExampleBatchClass implements Database.Batchable<sObject> {

    global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
// 得到需要处理的数据
} global void execute(Database.BatchableContext bc, List<P> records){
// 处理数据
} global void finish(Database.BatchableContext bc){
// 收尾函数
} }

执行批处理Apex类

使用“Database.executeBatch()”函数可以执行一个批处理Apex类。与此同时,还可以设定一个参数,指定从start()函数传入execute()函数的每批记录的最大数量(如果不设置则为200)。每次执行的时候,会返回一个ID类型的结果,通过这个ID可以在Salesforce中的AsyncApexJob对象中查询当前批处理的执行情况。

示例代码:

// 执行批处理类
ExampleBatchClass exampleBatchClass = new ExampleBatchClass(); // 不设定每批记录的最大数量并执行批处理操作
Id batchId = Database.executeBatch(exampleBatchClass); // 不设定每批记录的最大数量并执行批处理操作
Id batchIdWithVolumn = Database.executeBatch(exampleBatchClass, 300); // 查询批处理任务的进度
AsyncApexJob job = [SELECT
Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors
FROM AsyncApexJob
WHERE ID = :batchId];

在批处理Apex类中记录数据的状态

通过实现“Database.Stateful”接口可以在批处理类中记录数据被处理的状态,并可以在类中设定非静态变量,该变量的值在处理每批数据时不会自动刷新。

示例代码:

global class ExampleBatchClass implements Database.Batchable<sObject>, Database.Stateful {

    // 定义一个非静态变量,记录execute执行的次数
global Integer executeProcessed = 0; global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
} global void execute(Database.BatchableContext bc, List<P> records){
// 每次执行execute()函数,便增加1
executeProcessed += 1;
} global void finish(Database.BatchableContext bc){
// 在此可以显示总共执行了多少次execute()函数
System.debug(executeProcessed);
} }

批处理Apex类的单元测试

对批处理Apex类进行单元测试时,与Future函数类似,执行代码要包含在“Test.startTest()”和“Test.stopTest()”函数中。并且,在单元测试中,只能处理一个批次的记录,所以在定义测试数据时,不要让测试数据总数超过200条。

Queueable接口

Queueable接口的作用和Future函数类似,但是实现了Queueable的类可以使用sObject对象作为参数进行操作,这一点Future函数做不到。

Queueable接口中定义了“execute()”函数。它必须是global或者public的。

“System.enqueueJob()”函数可以将实现了Queueable接口的类加入系统的队列,异步执行。

代码结构如下:

public class ExampleClass implements Queueable {
public void execute(QueueableContext context) {
// ...
}
}

示例代码:

首先定义一个Apex类,实现Queueable接口。该类的功能是对于每一个Account对象,设置parentId字段并更新:

public class UpdateParentAccount implements Queueable {

    private List<Account> accounts;
private ID parent; public UpdateParentAccount(List<Account> records, ID id) {
this.accounts = records;
this.parent = id;
} public void execute(QueueableContext context) {
for (Account account : accounts) {
account.parentId = parent;
}
update accounts;
} }

然后在代码中调用此类,实现异步操作。假设已经有了一个Account列表和一个ID值:

// 设定参数
UpdateParentAccount updateJob = new UpdateParentAccount(accountList, parentId); // 执行该操作
ID jobID = System.enqueueJob(updateJob);

通过此代码,即可实现异步将accountList列表中的Account对象的parentId字段更新为参数parentId的值。

实现Queueable的类的单元测试

对实现了Queueable的Apex类进行单元测试时,与Future函数类似,执行代码要包含在“Test.startTest()”和“Test.stopTest()”函数中。比如:

UpdateParentAccount updateJob = new UpdateParentAccount(accountList, parentId);

Test.startTest();
// 执行操作的代码要放在startTest()和stopTest()之间
ID jobID = System.enqueueJob(updateJob);
Test.stopTest();

任务的连续处理

Queueable接口的另一个优点是可以在execute()函数中调用另一个实现了Queueable的类,实现任务的连续处理。比如:

public class FirstJob implements Queueable {
public void execute(QueueableContext context) {
// 连接下一个任务
System.enqueueJob(new SecondJob());
}
}

任务的连续处理最多可以同时处理50个任务,并且在连接任务时,每个execute()函数中只能连接一个任务。

Schedulable接口

当一个类实现了Schedulable接口之后,可以被作为计划任务,在特定的时间执行。

Schedulable接口中定义了“execute()”函数。它必须是global或者public的。

代码结构如下:

global class SchedulableExampleClass implements Schedulable {
global void execute(SchedulableContext ctx) {
// ...
}
}

“System.Schedule()”或“System.scheduleBatch()”函数可以设置实现了Schedulable的类在某个特定时间执行。调用Schedulable类和调用Queueable类的方式类似:

示例代码:定义Apex类的计划执行

String CRON_EXP = '0 0 06 * * ?';
String jobId = System.schedule('Scheduled Job Name', CRON_EXP, new SchedulableExampleClass());

在上述代码中,使用了System.schedule()函数。它包含三个参数:

  • 第一个参数是计划任务的名字,可以自己定义,不能与已经存在的计划任务名字重复
  • 第二个参数是cron job的表达式,具体可以参考维基百科crontab。一个基本的解释:表达式包含7个部分,从左到右依次是:秒、分钟、小时、日、月、星期几、年,其中后两个是可选项。“*”表示所有可能的值,比如对于“小时”就是包括了0点到23点。“?”表示任一可能的值,比如对于“星期几”就是每周任意一天。
  • 第三个参数是要被计划的实现了Schedulable接口的类

关于cron job的表达式

其格式为:

Seconds Minutes Hours Day_of_month Month Day_of_week Optional_year

可以用问号(?)来表示“每一个”。比如上面代码中的“0 0 06 * * ?”就代表了每天6点。“0 0 10 ? * MON-FRI”就代表了周一到周五早上10点。

Apex类计划执行的特点

  • 所有计划都要定义一个执行的时间点,比如早上8点。如果在同一时间有多个Apex类被设定为计划执行,则它们其中的一部分的执行有可能有延迟
  • Salesforce中可以定义计划执行的Apex类最多是100个

用CronTrigger追踪计划任务

System.schedule()函数返回的值是ID类型,代表了当前计划任务的ID。使用CronTrigger对象可以追踪此任务的情况。

在知道计划任务ID的时候,可以使用以下查询语句:

CronTrigger ct = [SELECT TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :jobId];

如果在实现了Schedulable接口的Apex类中,在execute()函数里需要查询当前计划任务的ID,可以使用SchedulableContext的getTriggerId()函数。

CronTrigger ct = [SELECT TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :sc.getTriggerId()];

关于SchedulableContext的详细信息可以参考官方文档

用CronJobDetail得到计划任务的详细信息

CronJobDetail对象和CronTrigger对象相关联。可以使用如下查询得到计划任务的详细信息:

CronTrigger job = [SELECT Id, CronJobDetail.Id, CronJobDetail.Name, CronJobDetail.JobType
FROM CronTrigger
ORDER BY CreatedDate DESC
LIMIT 10];

注意这里的CronJobDetail.JobType字段,此字段代表了所有任务的类型,对于Apex计划任务,此字段的值为7。

比如:

SELECT COUNT() FROM CronTrigger WHERE CronJobDetail.JobType = '7'

单元测试Apex计划任务

对实现了Schedulable的Apex类进行单元测试时,与Future函数类似,执行代码要包含在“Test.startTest()”和“Test.stopTest()”函数中。比如:

// 定义计划任务类
global class SchedulableExampleClass implements Schedulable {
global void execute(SchedulableContext sc) {
Account a = [SELECT Id, Name FROM Account WHERE Name = 'TEST ACCOUNT'];
a.Name = 'CHANGED NAME'; update a;
}
} // 单元测试类
@isTest
class TestScheduleExample {
@isTest
static void testExample() { // 开始测试
Test.startTest(); // 建立测试数据
Account a = new Account();
a.Name = 'TEST ACCOUNT';
insert a; // 建立计划任务的执行
String jobId = System.schedule('Scheduled Job', '0 0 0 3 9 ? 2022', new SchedulableExampleClass()); // 得到计划任务的信息
CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime
FROM CronTrigger
WHERE Id = :jobId]; // 检查计划任务的Cron表达式是否相同
System.assertEquals('0 0 0 3 9 ? 2022', ct.CronExpression); // 检查计划任务是否尚未运行
System.assertEquals(0, ct.TimesTriggered); // 检查计划任务下次运行时间
System.assertEquals('2022-09-03 00:00:00', String.valueOf(ct.NextFireTime)); // 检查数据是否尚未改变
System.assertNotEquals('CHANGED NAME', [SELECT Id, Name FROM Account WHERE Id = :a.Id].Name); // 完成测试
Test.stopTest();
// 在stopTest()执行之后,已经计划的任务会忽略Cron表达式定义的时间立即执行,并且是同步执行,所以可以保证在执行接下来的语句之前,计划的Apex类的功能已经完成了 // 检查数据是否改变了
System.assertEquals('CHANGED NAME', [SELECT Id, Name FROM Account WHERE Id = :a.Id].Name);
}
}

通过设置界面设置Apex类计划执行

在“设置”界面中,搜索“Apex 类”,可以进入“Apex 类”界面。在此界面中列出了所有系统中存在的Apex类。点击“计划Apex”按钮,即可计划一个实现了Schedulable接口的Apex类,以每周或每月为间隔自动执行。

异步 Apex 类的更多相关文章

  1. 从网络服务生成Apex类

    使用WSDL2Apex从网络服务生成Apex类 如果某个网络服务被定义在WSDL文件中,而Salesforce必须使用SOAP和网络服务进行通信,则这种情况在某些时候会为开发者带来很多麻烦.为了简化S ...

  2. C# 异步工具类 及一点小小的重构经验

    2015年新年第一篇随笔, 祝福虽然有些晚,但诚意还在:新年快乐. 今天主要是想分享一异步工具类,在C/S架构中.先进行网络资源异步访问,然后将回调函数 Invoke到UI线程中进行UI处理. 这样的 ...

  3. C#操作Control异步工具类

    /// <summary> /// 异步工具类 /// </summary> public class TaskTools { /// <summary> /// ...

  4. Java8 异步编排类CompletableFuture

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. https://www.cnblogs.com/shijiaqi1066/p/8758206 ...

  5. 异步IO类

    也学习多线程一段时间了,也写了几个简单实用的功能类,也意思到细节的处理的重要性,现在就让我们来写一个稍稍更有用的异步IO的类. 本来想参考Java NIO 中的类,Java NIO作为新io包,本身提 ...

  6. ReactNative: 使用AsyncStorage异步存储类

    一.简介 AsyncStorage是一个简单的具有异步特性可持久化的键值对key-value的存储系统.它对整个APP而言,是一个全局的存储空间,可以用来替代H5中提供的window属性LocalSt ...

  7. AsyncTask异步任务类使用学习

    new MyAsyncTask() .execute("http://pic.baike.soso.com/p/20120716/bki-20120716095331-640956396.j ...

  8. Task Class .net4.0异步编程类

    文章:Task Class 地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netfra ...

  9. Apex 的 Trigger 类简介

    Apex Triggers Apex 触发器(Apex Triggers)是一种特殊的 Apex 类.它的主要作用是在一条记录被插入.修改.删除之前或之后自动执行一系列的操作.每一个 Trigger ...

随机推荐

  1. 深圳scala-meetup-20180902(2)- Future vs Task and ReaderMonad依赖注入

    在对上一次3月份的scala-meetup里我曾分享了关于Future在函数组合中的问题及如何用Monix.Task来替代.具体分析可以查阅这篇博文.在上篇示范里我们使用了Future来实现某种non ...

  2. 使用广播-BroadcastReceiver最详细解析

    女孩:BroadcastReceiver是什么呀? 男孩:Broadcast是广播的意思,在Android中应用程序之间的传输信息的机制,BroadcastReceiver是接收广播通知的组件,广播和 ...

  3. 浅谈css3长度单位rem,以及移动端布局技巧

    rem是什么? rem是css3中新增加的一个单位属性(font size of the root element),根据页面的根节点的字体大小进行转变的单位.root!!!!!!!!!根节点,也就是 ...

  4. [CocoaPods]使用Trunk进行设置

    CocoaPods Trunk CocoaPods Trunk是一种身份验证和CocoaPods API服务.要将新的或更新的库发布到CocoaPods以进行公开发布,您需要在Trunk中注册并在当前 ...

  5. 在Shell脚本中获取指定进程的PID

    注意这条命令用反引号(Tab上面的那个键)括起来,作用类似于${ } processId = ` ps -ef | grep fms.jar | grep -v grep | awk '{print ...

  6. Linux - 查看进程状态

    ps命令 report a snapshot of the current processes. 能提供一份当前进程的快照,以列表的形式显示正在运行的进程. 列出进程的数量取决于命令所附加的参数,例如 ...

  7. Jenkins和gitlab集成自动构建

    Jenkins安装插件 Jenkins上需要安装如下插件 Gitlab Hook Plugin,GitLab Plugin Job配置 在需要自动触发的Job中 选择Build Triggers进行如 ...

  8. Django model update的各种用法介绍

    Django开发过程中对表(model)的增删改查是最常用的功能之一,本文介绍笔者在使用model update过程中遇到的那些事 model update常规用法 假如我们的表结构是这样的 clas ...

  9. 关于appendChild和insertBefore appendTo()和append

    appendChild和insertBefore(原生js) appendTo()和append(jquery)

  10. IE不支持 Promise 解决办法

    引入 <script src = "https://cdn.polyfill.io/v2/polyfill.min.js"></script> 或 < ...