Apex 异步(6)监视异步

学习目标

完成这个单位后,你会知道:

  • 如何监控不同类型的工作。
  • 如何使用弹性队列。

监视异步作业

异步工作的好处是他们在后台默默地工作。异步工作的难点在于他们在后台默默工作。幸运的是,有几种方法可以监视你的工作内容。

您可以监视Salesforce用户界面中所有作业的状态。从安装程序中,在快速查找框中输入作业,然后选择Apex作业。

Apex Jobs页面显示所有异步Apex作业,其中包含有关每个作业执行的信息。以下屏幕截图显示了同一个Batch Apex类的一个未来方法作业和两个已完成的批处理作业。

Apex Jobs

如果您有许多批处理作业,请使用“批处理作业”页面仅查看批处理作业。要打开“Apex批处理作业”页面,请单击“Apex作业”页面顶部的链接。使用“Apex批处理作业”页面中的滑块选择特定的日期范围,并缩小显示的批处理作业的列表。您可以查看过去尚未删除的作业。 “批处理作业”页面按批处理类别对作业进行分组。

Apex Batch Jobs

点击您感兴趣的课程ID旁边的“更多信息”,查找有关为该课程执行的作业的详细信息。此图像显示单击“更多信息”后显示的弹出窗口。这个批次类有两个成功执行的作业。

information about parent jobs

您还可以监视Apex Flex队列中Apex作业的状态,并对其进行重新排序以控制首先处理哪些作业。从“设置”中,在“快速查找”框中输入作业,然后选择 Apex Flex Queue.

监测Future Jobs

Future jobs就像任何其他工作一样出现在Apex工作页面上。但是,未来的工作目前不属于灵活队列的一部分。

你可以查询AsyncApexJob找到你未来的工作,但有一个警告。由于启动未来作业不会返回ID,因此您必须过滤其他字段(如MethodName或JobType)以查找您的作业。在这个Stack Exchange文章中有几个示例SOQL查询可能会有所帮助。

使用SOQL监视Queued Jobs

要查询有关您提交的作业的信息,请通过筛选System.enqueueJob方法返回的作业ID,在AsyncApexJob上执行SOQL查询。

AsyncApexJob jobInfo = [SELECT Status, NumberOfErrors
    FROM AsyncApexJob WHERE Id = :jobID];

使用Flex队列监视Queue Jobs

Apex Flex队列使您能够提交多达100个批处理作业执行。任何提交执行的作业都处于保持状态,并放置在Apex Flex队列中。最多可以有100个批处理作业处于保持状态。

按照先进先出的顺序处理作业。您可以查看当前的队列顺序并将其排列,以便将重要的工作移到前面,或者将重要的工作移到后面。

当系统资源可用时,系统从Apex Flex队列的顶部选取下一个作业,并将其移至批处理作业队列。系统可以为每个组织同时处理最多五个排队或活动作业。这些已移动作业的状态从“保持”更改为“已排队”。排队的作业在系统准备好处理新作业时被执行。像其他作业一样,您可以在Apex Jobs页面中监控排队的作业。

监视Scheduled Jobs

在计划完Apex作业之后,您可以通过在CronTrigger上运行SOQL查询来获得更多关于它的信息。以下示例查询作业运行的次数以及计划再次运行作业的日期和时间。它使用从System.schedule方法返回的jobID变量。

CronTrigger ct = [SELECT TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :jobID];
如果您在可调度类的execute方法内部执行此查询,则可以通过调用SchedulableContext参数变量上的getTriggerId来获取当前作业的ID。
global class DoAwesomeStuff implements Schedulable {

    global void execute(SchedulableContext sc) {
        // 一些很棒的代码
        CronTrigger ct = [SELECT TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :sc.getTriggerId()];
    }
    
}
您还可以从与CronTrigger记录关联的CronJobDetail记录中获取作业的名称和作业的类型。为此,请在CronTrigger上执行查询时使用CronJobDetail关系。本示例使用CronJobDetail中的作业名称和类型来检索最新的CronTrigger记录。
CronTrigger job = [SELECT Id, CronJobDetail.Id, CronJobDetail.Name, CronJobDetail.JobType FROM CronTrigger ORDER BY CreatedDate DESC LIMIT 1];
或者,您可以直接查询CronJobDetail以获取作业的名称和类型。 以下示例获取上例中查询的CronTrigger记录的作业名称和类型。 相应的CronJobDetail记录标识由CronTrigger记录上的CronJobDetail.Id表达式获取。
CronJobDetail ctd = [SELECT Id, Name, JobType FROM CronJobDetail WHERE Id = :job.CronJobDetail.Id];
最后,要获取所有Apex计划作业的总计数(不包括所有其他计划作业类型),请执行以下查询。 请注意,值“7”是为作业类型指定的,该作业类型对应于预定的Apex作业类型。 请参阅参考资料部分中的CronJobDetail,了解所有类型的列表。
SELECT COUNT() FROM CronTrigger WHERE CronJobDetail.JobType = '7

Apex 异步(5)schedule

学习目标

完成这个单位后,你会知道:

  • 何时使用预定的Apex。
  • 如何监控计划的工作。
  • 计划的Apex语法。
  • 计划的方法最佳实践。

计划的Apex

Apex Scheduler让您延迟执行,以便您可以在指定的时间运行Apex类。对于使用Batch Apex的每日或每周维护任务来说,这是理想之选。要利用调度程序,编写一个实现Schedulable接口的Apex类,然后按照特定的调度安排它执行。

计划的Apex 语法

为了调用Apex类在特定时间运行,首先实现类的Schedulable接口。然后,使用System.schedule方法安排类的实例在特定时间运行。

global class SomeClass implements Schedulable {
    global void execute(SchedulableContext ctx) {
        // 真棒代码在这里
    }
}

该类实现了Schedulable接口,并且必须实现此接口包含的唯一方法,即execute方法。

这个方法的参数是一个SchedulableContext对象。在课程安排好之后,将创建一个CronTrigger对象来表示计划的作业。它提供了一个getTriggerId方法,该方法返回一个CronTrigger API对象的ID。

示例代码

这个类查询当前应该关闭的开放机会,并创建一个任务来提醒所有者更新机会。

global class RemindOpptyOwners implements Schedulable {

    global void execute(SchedulableContext ctx) {
        List<Opportunity> opptys = [SELECT Id, Name, OwnerId, CloseDate 
            FROM Opportunity 
            WHERE IsClosed = False AND 
            CloseDate < TODAY];
        // 为列表中的每个机会创建一个任务
        TaskUtils.remindOwners(opptys);
    }
    
}
您可以安排您的课程以编程方式或从Apex调度程序用户界面运行。

使用System.Schedule方法

在使用Schedulable接口实现类之后,使用System.Schedule方法执行它。 System.Schedule方法使用用户的时区作为所有调度的基础,但是以系统模式运行 – 所有类都被执行,无论用户是否有执行类的权限。

注意

如果您计划通过触发器安排课程,请特别小心。您必须能够保证触发器不会添加比限制更多的计划作业分类。特别是,考虑API批量更新,导入向导,通过用户界面进行的大容量记录更改以及一次可以更新多个记录的所有情况。

System.Schedule方法有三个参数:作业的名称,用于表示作业计划运行的时间和日期的CRON表达式以及类的名称。

RemindOpptyOwners reminder = new RemindOpptyOwners();
// 秒分钟小时数Day_of_month Month Day_of_week optional_year
String sch = '20 30 8 10 2 ?';
String jobID = System.schedule('Remind Opp Owners', sch, reminder);
有关用于计划的CRON表达式的更多信息,请参阅Apex Scheduler中的“使用System.Schedule方法”部分。


从UI调度作业

您也可以使用用户界面安排课程。

  1. 从安装程序中,在快速查找框中输入Apex,然后选择 Apex Classes.
  2. 点击 Schedule Apex.
  3. 对于工作名称,请输入类似Daily Oppty Reminder的内容。
  4. 单击Apex类旁边的查找按钮,然后为搜索项输入*以获取所有可以调度的类的列表。在搜索结果中,单击您的计划课程的名称。
  5. 为频率选择每周或每月,并设置所需的频率。
  6. 选择开始和结束日期,以及首选开始时间。
  7. 点击 Save.

测试计划的 Apex

就像我们到目前为止所介绍的其他异步方法一样,对于Scheduled Apex,您还必须确保计划的作业在测试结果之前完成。为此,请在System.schedule方法周围再次使用startTest和stopTest,以确保在继续测试之前完成处理。

@isTest
private class RemindOppyOwnersTest {

    // CRON表情:3月15日午夜。
    // 因为这是一个测试,所以在Test.stopTest()之后立即执行作业。
    public static String CRON_EXP = '0 0 0 15 3 ? 2022';

    static testmethod void testScheduledJob() {

        // 创建一些过时的机会记录
        List<Opportunity> opptys = new List<Opportunity>();
        Date closeDate = Date.today().addDays(-7);
        for (Integer i=0; i<10; i++) {
            Opportunity o = new Opportunity(
                Name = 'Opportunity ' + i,
                CloseDate = closeDate,
                StageName = 'Prospecting'
            );
            opptys.add(o);
        }
        insert opptys;
        
        // 获取我们刚刚插入的机会的ID
        Map<Id, Opportunity> opptyMap = new Map<Id, Opportunity>(opptys);
        List<Id> opptyIds = new List<Id>(opptyMap.keySet());

        Test.startTest();
        // 安排测试工作
        String jobId = System.schedule('ScheduledApexTest',
            CRON_EXP, 
            new RemindOpptyOwners());         
        // 验证计划作业尚未运行。
        List<Task> lt = [SELECT Id 
            FROM Task 
            WHERE WhatId IN :opptyIds];
        System.assertEquals(0, lt.size(), 'Tasks exist before job has run');
        // 停止测试将同步运行作业
        Test.stopTest();
        
        // 现在计划的作业已经执行,
        // 检查我们的任务是否被创建
        lt = [SELECT Id 
            FROM Task 
            WHERE WhatId IN :opptyIds];
        System.assertEquals(opptyIds.size(), 
            lt.size(), 
            'Tasks were not created');

    }
}

要记住的事情

计划的Apex包含许多需要注意的项目(有关详细信息,请参阅参考资料部分中的Apex Scheduler),但一般情况下:
  • 一次只能有100个预定的Apex作业,每24小时有最多的预定Apex执行次数。有关详细信息,请参阅参考资料部分中的执行调控器和限制。
  • 如果您计划通过触发器安排课程,请特别小心。您必须能够保证触发器不会添加比限制更多的计划作业。
  • 计划的Apex不支持同步Web服务标注。为了能够进行标注,通过将标注放在用@future(callout = true)注释的方法中进行异步标注,并从预定的Apex中调用此方法。但是,如果预定的Apex执行批处理作业,则批处理类将支持标注。

Apex 异步(4)enqueue

学习目标

完成这个单位后,你会知道:

  • 何时使用Queueable接口。
  • 排队和未来方法之间的区别。
  • 可排队的Apex语法。
  • 可排队方法的最佳实践。

Queueable Apex

Queueable Apex在15年冬季发布,基本上是未来方法的一个超集,并带有一些额外的优势。我们把未来方法的简单性和Batch Apex的力量混合在一起形成了Queueable Apex!它为您提供了平台为您序列化的类结构,简化的界面,无需启动和结束方法,甚至允许您使用不仅仅是原始参数!它由一个简单的System.enqueueJob()方法调用,该方法返回一个可以监视的作业ID。它击败切片面包手!

Queueable Apex允许您提交与未来方法类似的异步处理作业,并具有以下附加优势:

  • 非原始类型:您的Queueable类可以包含非原始数据类型的成员变量,如sObjects或自定义Apex类型。这些对象可以在作业执行时被访问。
  • 监视:通过调用System.enqueueJob方法提交作业时,该方法将返回AsyncApexJob记录的ID。您可以使用此ID来标识您的作业并监视其进度,可以通过Apex Jobs页面中的Salesforce用户界面,也可以通过从AsyncApexJob查询记录来以编程方式进行监视。
  • 链接工作:通过从正在运行的工作中启动第二份工作,可以将一份工作链接到另一份工作上。链接作业是有用的,如果你需要做一些顺序处理。

 

Queueable与Future

因为可排队的方法在功能上等同于未来的方法,所以大多数情况下你可能希望使用可排队方法而不是将来的方法。但是,这并不意味着您现在应该回头重构所有未来的方法。如果您未来的方法超出了总督限制,或者如果您认为未来的方法需要更高的限制,则可以使用“未来方法更高限制”试验增加未来方法的限制。

使用未来的方法而不是可排队的另一个原因是当你的功能有时被同步执行,有时是异步执行的。以这种方式重构一个方法要比转换成一个可排队的类容易得多。当您发现现有代码的一部分需要移动到异步执行时,这很方便。你可以简单地创建一个相似的将来的方法来包装你的同步方法,就像这样:

@future
static void myFutureMethod(List<String> params) {
    // 调用同步方法
    mySyncMethod(params);
}
Queueable 语法

To use Queueable Apex, simply implement the Queueable interface.

public class SomeClass implements Queueable { 
    public void execute(QueueableContext context) {
        // 真棒代码在这里
    }
}

示例代码

一个常见的情况是采用一些sObject记录集,执行一些处理,例如向外部REST端点发出调用,或执行一些计算,然后异步地在数据库中更新它们。由于@future方法仅限于原始数据类型(或数组或基元集合),所以可排队的Apex是理想的选择。下面的代码采取客户记录的集合,为每个记录设置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;
    }
    
}
要将此类添加为队列中的作业,请执行以下代码:
// 找到'NY'的所有客户
List<Account> accounts = [select id from account where billingstate = ‘NY’];
// 为所有记录找到一个特定的父客户
Id parentId = [select id from account where name = 'ACME Corp'][0].Id;

// 实例化一个Queueable类的新实例
UpdateParentAccount updateJob = new UpdateParentAccount(accounts, parentId);

// 排队处理作业
ID jobID = System.enqueueJob(updateJob);

提交可执行的队列类之后,作业将被添加到队列中,并在系统资源可用时进行处理。

您可以使用新的作业ID来监视进度,无论是通过Apex Jobs页面还是以编程方式查询AsyncApexJob:

SELECT Id, Status, NumberOfErrors FROM AsyncApexJob WHERE Id = :jobID

测试Queueable Apex

以下代码示例演示如何在测试方法中测试可排队作业的执行情况。它看起来非常类似于Batch Apex测试。为了确保可排队的进程在测试方法内运行,作业被提交到Test.startTest和Test.stopTest块之间的队列中。在Test.stopTest语句之后,系统同步执行在测试方法中启动的所有异步进程。接下来,测试方法通过查询作业更新的客户记录来验证可排队作业的结果。

@isTest
public class UpdateParentAccountTest {

    @testSetup 
    static void setup() {
        List<Account> accounts = new List<Account>();
        // 添加一个父客户
        accounts.add(new Account(name='Parent'));
        // 添加100个子客户
        for (Integer i = 0; i < 100; i++) {
            accounts.add(new Account(
                name='Test Account'+i
            ));
        }
        insert accounts;
    }
    
    static testmethod void testQueueable() {
        // 查询测试数据传递给队列类
        Id parentId = [select id from account where name = 'Parent'][0].Id;
        List<Account> accounts = [select id, name from account where name like 'Test Account%'];
        // 创建我们的Queueable实例
        UpdateParentAccount updater = new UpdateParentAccount(accounts, parentId);
        // startTest / stopTest block强制异步进程运行
        Test.startTest();        
        System.enqueueJob(updater);
        Test.stopTest();        
        // 验证作业运行。检查记录现在是否有正确的parentId
        System.assertEquals(100, [select count() from account where parentId = :parentId]);
    }
    
}

 

Chaining Jobs

Queueable Apex的最大特点之一就是职位链。如果您需要连续运行作业,Queueable Apex可以让您的生活更轻松。要将作业链接到其他作业,请从可排队类的execute()方法提交第二个作业。您可以从正在执行的作业中仅添加一个作业,这意味着每个父作业只能存在一个子作业。例如,如果您有另一个名为SecondJob的类实现Queueable接口,则可以将此类添加到execute()方法中的队列中,如下所示:

public class FirstJob implements Queueable { 
    public void execute(QueueableContext context) { 
        //真棒处理逻辑在这里通过提交下一份工作把这份工作链接到下一份工作
        System.enqueueJob(new SecondJob());
    }
}

再次,测试有一个稍微不同的模式。您不能在Apex测试中链接可排队的作业,这样做会导致错误。为了避免令人讨厌的错误,您可以通过在链接作业之前调用Test.isRunningTest()来检查Apex是否在测试环境中运行。

 

要记住的事情

Queueable Apex是一个伟大的新工具,但有几件事值得注意:

  • 针对异步Apex方法执行的共享限制,排队作业的执行计数一次。
  • 在一个事务中,您可以使用System.enqueueJob将最多50个作业添加到队列中。
  • 在链接作业时,只能使用System.enqueueJob从正在执行的作业中添加一个作业,这意味着每个父排队作业只能存在一个子作业。从同一个可排队的作业开始多个子作业是一个禁忌。
  • 链式作业的深度没有限制,这意味着你可以链接一个作业到另一个作业,并重复这个过程,每个新的子作业链接到一个新的子作业。但是,对于Developer Edition和Trial orgs,链接作业的最大堆栈深度为5,这意味着您可以链接作业四次,链中的最大作业数为5,包括初始父排队作业。