Asynchronous Apex

Asynchronous Processing Basics

Explain the difference between synchronous and asynchronous processing. Choose which kind of asynchronous Apex to use in various scenarios.
  • Asynchronous Apex
    • Asynchronous processes are processes or functions that execute a task “in the background” without the user having to wait for the task to finish
      • Processes are run in a separate thread, at a later time
    • Typically asynchronous apex is used for callouts to external systems, operations that require higher limits, and code that needs to run at a certain time
    • Key Benefits:
      • User Efficiency: saves users from needing to wait for a long running process to complete
      • Scalability: allowing features of the platform to execute at some point in the future when there are more resources available allows the platform to handle more jobs using parallel processing
      • Higher Limits: asynchronous processes are started in a new thread, with higher governor and execution limits
    • Four types of asynchronous apex:
      • Future Methods: run in their own thread, do not start until resources are available. Commonly used for web service callouts.
      • Batch Apex: run large jobs that would exceed normal processing limits. Commonly used for data cleansing or archiving of records.
      • Queueable Apex: similar to future methods, but provide additional job chaining and allow more complex data types. Commonly used to perform sequential processing operations with external Web services.
      • Scheduled Apex: schedule Apex to run at a specified time. Commonly used for daily or weekly tasks.
    • Different types of asynchronous operations are not mutually exclusive - common pattern is to kick off Batch Apex job from a Scheduled Apex job
  • Increased Governor and Execution Limits
    • SOQL queries limit is doubled from 100 to 200 queries
    • Heap size and max CPU time are similarly larger
    • Governor limits are independent of the limits in the synchronous request that queued the async request initially
      • IE, two separate Apex invocations and more than double the processing capability
  • How Asynchronous Processing Works
    • A few challenges with asynchronous processing in a multitenant environment:
      • Ensure fairness of processing: make sure every customer gets a fair share
      • Ensure fault tolerance: ensure no asynchronous requests are lost due to equipment or software failures
    • Platform uses a queue-based asynchronous processing framework - three parts to the request lifecycle:
      • Enqueue: request is placed into the queue along with the appropriate data to process it
      • Persistance: requests are stored in persistent storage for failure recovery and provide transactional capabilities
      • Dequeue: request is removed from the queue and processed - if it fails, transaction control ensures requests are not lost
    • Requests are processed by a handler: handler is code that performs functions for a specific request type
      • Handlers are executed by a finite number of worker threads on each of the application servers that make up an instance
  • Resource Conservation
    • Asynchronous processing has lower priority than real-time interaction
    • Queueing framework monitors system resources like server memory and CPU usage and reduces asynchronous processing when thresholds are exceeded
    • So, there’s no guarantee on processing time, but it’ll all work out in the end

Use Future Methods

When to use future methods. The limitations of using future methods. How to use future methods for callouts. Future method best practices.
  • Future Apex
    • Future Apex is used to run processes in a separate thread at a later time when system resources become available
    • Use @future annotation to identify methods that run asynchronously, called “future methods”
    • Future methods are commonly used for:
      • Callouts to external Web services
      • Operations you want to run in their own thread, such as some sort of resource-intensive calculation or processing of records
      • Isolating DML operations on different sObject types to prevent the “mixed DML error
  • Future Method Syntax
    • Future methods must be static methods, can only return void, can’t take standard or custom objects as arguments
      • Common pattern is to pass a list of record IDs you want to process asynchronously
    • Remember that future methods are not guaranteed to execute in the same order as they are called
    • Also possible that two future methods could run concurrently, which could result in record locking and a runtime error if two methods were updating the same record
public class SomeClass {
  @future
  public static void someFutureMethod(List<Id> recordIds) {
    List<Account> accounts = [Select Id, Name from Account Where Id IN :recordIds];
    // process account records to do awesome stuff
  }
}
  • Sample Callout Code
    • Class below has methods for making the callout both synchronously and asynchronously where callouts are not permitted
public class SMSUtils {
    // Call async from triggers, etc, where callouts are not permitted.
    @future(callout=true)
    public static void sendSMSAsync(String fromNbr, String toNbr, String m) {
        String results = sendSMS(fromNbr, toNbr, m);
        System.debug(results);
    }
    // Call from controllers, etc, for immediate processing
    public static String sendSMS(String fromNbr, String toNbr, String m) {
        // Calling 'send' will result in a callout
        String results = SmsMessage.send(fromNbr, toNbr, m);
        insert new SMS_Log__c(to__c=toNbr, from__c=fromNbr, msg__c=results);
        return results;
    }
}
  • Test Classes
    • Testing future methods is different than typical Apex testing
    • Enclose test code between startTest() and stopTest() test methods to test future methods - the system will collect all asynchronous calls made after the startTest(), then run them synchronously when stopTest() is executed.
    • Note the first test class uses a “mock” responses instead of making an actual callout to the REST API endpoint
    • The second test class contains a test method that tests both the asynchronous and synchronous methods as the former calls the latter
@isTest
public class SMSCalloutMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest req) {
        // Create a fake response
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody('{"status":"success"}');
        res.setStatusCode(200);
        return res;
    }
}
@IsTest
private class Test_SMSUtils {
  @IsTest
  private static void testSendSms() {
    Test.setMock(HttpCalloutMock.class, new SMSCalloutMock());
    Test.startTest();
      SMSUtils.sendSMSAsync('111', '222', 'Greetings!');
    Test.stopTest();
    // runs callout and check results
    List<SMS_Log__c> logs = [select msg__c from SMS_Log__c];
    System.assertEquals(1, logs.size());
    System.assertEquals('success', logs[0].msg__c);
  }
}
  • Best Practices
    • Avoid design patterns that add large numbers of future requests over a short period of time since every future method invocation adds one request to the asynchronous queue
      • If design could add 2000 or more requests at a time, requests could get delayed due to flow control
    • Other best practices:
      • Ensure that future methods execute as fast as possible
      • Bundle webb service callouts together from the same future method instead of using separate future methods for each callout
      • Conduct thorough testing at scale - ex: test that a trigger enqueueing the @future calls is able to handle a trigger collection of 200 records
      • Consider using Batch AApex instead of future methods to process large numbers of records asynchronously - this is more efficient than creating a future request for each record
  • Other things to Remember
    • Future methods can’t be used in Visualforce controllers in getMethodName(), setMethodName(), nor in the constructor
    • Can’t call a future method from a future method, or invoke a trigger that calls a future method while running a future method
    • Can’t use the getContent() and getContentAsPDF() methods in future methods
    • Limited to 50 future calls per Apex invocation, and there’s a limit on future calls per 24-hour period as well
public without sharing class AccountProcessor {

    @future
    public static void countContacts(List<Id> accountIds) {
        List<Account> accounts = [SELECT Id, 
                                         (SELECT Id FROM Contacts) 
                                    FROM Account 
                                   WHERE Id IN :accountIds];
        
        for (Account acc: accounts) {
            acc.Number_of_Contacts__c = acc.Contacts.size();
        }
        
        update accounts;
    }
}
@isTest
private class AccountProcessorTest {
	
    @isTest
    private static void countContactsTest() {
        
        // Load test data
        List<Account> accounts = new List<Account>();
        for (Integer i=0; i<300; i++) {
            accounts.add(new Account(Name='Test Account' + i));
        }
        insert accounts;
        
        List<Contact> contacts = new List<Contact>();
        List<Id> accountIds = new List<Id>();
        for (Account acc: accounts) {
            contacts.add(new Contact(FirstName=acc.Name, 
                                     LastName='TestContact', 
                                     AccountId=acc.Id));
        	accountIds.add(acc.Id);
        }
        insert contacts;
        
        // Do the test
        Test.startTest();
        AccountProcessor.countContacts(accountIds);
        Test.stopTest();
        
        // Check the result
        List<Account> accs = [SELECT Id, 
                                     Number_of_Contacts__c FROM Account];
        for (Account acc : accs) {
            System.assertEquals(1, 
                                acc.Number_of_Contacts__c, 
                                'Error: At least 1 Account with incorrect Number of Contacts');
        }
    }
}

Use Batch Apex

Where to use Batch Apex. The higher Apex limits when using batch. Batch Apex syntax. Batch Apex best practices.
  • Batch Apex
    • Batch Apex is used to run large jobs (thousands/millions of records) that would normally exceed processing limits
    • If you have a lot of records to process (data cleansing or archiving, for example), Batch Apex is probably your best solution
    • Under the hood:
      • Execution logic of the batch class is called once for each batch
      • Each batch is placed on the Apex job queue and is executed as a discrete transaction. This means:
        • Every transaction starts with a new set of governor limits, so it is easier to ensure code stays within execution limits
        • If one batch fails, all other successful batches aren’t rolled back
  • Batch Apex Syntax
    • Batch Apex classes must implement the Database.Batchable interface and include the following methods:
      • start: used to collect records or objects to be passed to the interface method execute for processing
        • Returns either a Database.QueryLocator object or an Iterable containing the records or bojects passed to the job
          • Generally Database.QueryLocator does the trick, use Iterable for more advanced scenarios
      • execute: performs the actual processing for each chunk of “batch” of data passed to the method
        • Default batch size is 200 records
        • Not guaranteed to execute in the order they are received from the start method
        • Takes a reference to the Database.BatchableContext object and a list of sObjects such as List<sObject> or a list of parameterized types
      • finish: used to execute post-processing operations (such as sending an email) called once after all batches are processed
    • Skeleton of a Batch Apex class:
public class MyBatchClass implements Database.Batchable<sObject> {
    public (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
        // collect the batches of records or objects to be passed to execute
    }
    public void execute(Database.BatchableContext bc, List<P> records){
        // process each batch of records
    }
    public void finish(Database.BatchableContext bc){
        // execute any post-processing operations
    }
}
  • Invoking a Batch Class
    • To invoke, instantiate it and then call Database.executeBatch with the instance
    • Pass a second scope parameter to specify number of records
    • Check a batch job’s progress with AsyncApexJob records or manage the job in the Apex Job Queue
// To invoke
MyBatchClass myBatchObject = new MyBatchClass();
Id batchId = Database.executeBatch(myBatchObject);
// To pass a second parameter specifying number of records
Id batchId = Database.executeBatch(myBatchObject, 100);
// To check progress after invocation
AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, 
                           TotalJobItems, NumberOfErrors 
                      FROM AsyncApexJob 
                     WHERE ID = :batchId ];
  • Using State in Batch Apex
    • Batch Apex is typically stateless - each execution is considered a discrete transaction
    • But, if you specify Database.Stateful in the class definition, you can maintain state across all transactions
      • When using, only instance member variables retain their values between transactions
      • This is useful for counting or summarizing records as they’re processed
  • Sample Batch Apex Code
    • Following is practical example - Consider a business requirement that states all contacts for companies in the US must have their parent company’s billing address as their mailing address
    • Following finds all account records and updates the asocated contacts with their account’s mailing address, then sends an email with the results of the bulk job and the number of records updated
public class UpdateContactAddresses implements
    Database.Batchable<sObject>, Database.Stateful {
    // instance member to retain state across transactions
    public Integer recordsProcessed = 0;
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT ID, BillingStreet, BillingCity, BillingState, ' +
            'BillingPostalCode, (SELECT ID, MailingStreet, MailingCity, ' +
            'MailingState, MailingPostalCode FROM Contacts) FROM Account ' +
            'Where BillingCountry = \'USA\''
        );
    }
    public void execute(Database.BatchableContext bc, List<Account> scope){
        // process each batch of records
        List<Contact> contacts = new List<Contact>();
        for (Account account : scope) {
            for (Contact contact : account.contacts) {
                contact.MailingStreet = account.BillingStreet;
                contact.MailingCity = account.BillingCity;
                contact.MailingState = account.BillingState;
                contact.MailingPostalCode = account.BillingPostalCode;
                // add contact to list to be updated
                contacts.add(contact);
                // increment the instance member counter
                recordsProcessed = recordsProcessed + 1;
            }
        }
        update contacts;
    }
    public void finish(Database.BatchableContext bc){
        System.debug(recordsProcessed + ' records processed. Shazam!');
        AsyncApexJob job = [SELECT Id, Status, NumberOfErrors,
            JobItemsProcessed,
            TotalJobItems, CreatedBy.Email
            FROM AsyncApexJob
            WHERE Id = :bc.getJobId()];
        // call some utility to send email
        EmailUtils.sendMessage(job, recordsProcessed);
    }
}
  • Testing Batch Apex
    • Following demonstrate how to test the above batch class - inserts some records, calls the Batch Apex class, then asserts the records were updated properly
    • Note that test methods can execute only one batch, and you should also ensure the Iterable returned by the start() method matches the batch size
@isTest
private class UpdateContactAddressesTest {
    @testSetup
    static void setup() {
        List<Account> accounts = new List<Account>();
        List<Contact> contacts = new List<Contact>();
        // insert 10 accounts
        for (Integer i=0;i<10;i++) {
            accounts.add(new Account(name='Account '+i,
                billingcity='New York', billingcountry='USA'));
        }
        insert accounts;
        // find the account just inserted. add contact for each
        for (Account account : [select id from account]) {
            contacts.add(new Contact(firstname='first',
                lastname='last', accountId=account.id));
        }
        insert contacts;
    }
    @isTest static void test() {
        Test.startTest();
        UpdateContactAddresses uca = new UpdateContactAddresses();
        Id batchId = Database.executeBatch(uca);
        Test.stopTest();
        // after the testing stops, assert records were updated properly
        System.assertEquals(10, [select count() from contact where MailingCity = 'New York']);
    }
}
  • Best Practices
    • Only use Batch Apex if you have more than one batch of records. Otherwise, use Queueable Apex
    • Tune any SOQL query to gather records to execute as quickly as possible
    • minimize number of asynchronous requests created to minimize chance of delays
    • Use extreme care if planning to invoke a batch job from a trigger - you need to guarantee that the trigger won’t add more batch jobs than the limit
@isTest
public class LeadProcessorTest {
	
    @isTest
    private static void testBatchClass() {
        
        // Load test data
		List<Lead> leads = new List<Lead>();
        for (Integer i=0; i<200; i++) {
            leads.add(new Lead(LastName='Connock', Company='Salesforce'));
        }
        insert leads;
        
        // Perform the test
        Test.startTest();
        LeadProcessor lp = new LeadProcessor();
        Id batchId = Database.executeBatch(lp, 200);
        Test.stopTest();
        
        // Check the result
        List<Lead> updatedLeads = [SELECT Id
                                     FROM Lead
                                    WHERE LeadSource = 'Dreamforce'];
        System.assertEquals(200, 
                            updatedLeads.size(), 
                            'Error: At least 1 Lead record not updated correctly');
    }
}
@isTest
public class LeadProcessorTest {
	
    @isTest
    private static void testBatchClass() {
        
        // Load test data
		List<Lead> leads = new List<Lead>();
        for (Integer i=0; i<200; i++) {
            leads.add(new Lead(LastName='Connock', Company='Salesforce'));
        }
        insert leads;
        
        // Perform the test
        Test.startTest();
        LeadProcessor lp = new LeadProcessor();
        Id batchId = Database.executeBatch(lp, 200);
        Test.stopTest();
        
        // Check the result
        List<Lead> updatedLeads = [SELECT Id
                                     FROM Lead
                                    WHERE LeadSource = 'Dreamforce'];
        System.assertEquals(200, 
                            updatedLeads.size(), 
                            'Error: At least 1 Lead record not updated correctly');
    }
}

Control Processes with Queueable Apex

When to use the `Queueable` interface. The differences between queueable and future methods. Queueable Apex syntax. Queueable method best practices.
  • Queueable Apex
    • Queueable Apex combines the simplicity of future methods and the power of Batch Apex
    • Queueable Apex provides a class structure that the platform serializes for you
    • Called by System.enqueueJob() method that returns a job ID
    • Queueable Apex lets you submit jobs for asynchronous processing similar to future methods, but with added benefits:
      • Non-primitive types: member variables can include non-primitive types like sObjects or custom Apex types
      • Monitoring: System.engueueJob() returns the ID of the AsyncApexJob record
      • Chaining jobs: chain one job to another job by starting a second job from a running job. Useful if you need to do sequential processing.
  • Queueable Versus Future
    • Queueable methods are functionally equivalent to future methods, so most of the time you’ll probably want to use queueable instead
    • Use future methods instead of queueable when functionality is sometimes executed synchronously and sometimes asynchronously
      • Easier to refactor a method that way than it is to convert to a queueable class
      • Just create a similar future method that wraps your synchronous method like this:
@future
static void myFutureMethod(List<String> params) {
    // call synchronous method
    mySyncMethod(params);
}
  • Queueable Syntax
    • To use Queueable Apex, implement Queueable interface
public class SomeClass implements Queueable {
    public void execute(QueueableContext context) {
        // awesome code here
    }
}
  • Sample Code
    • Common use for Queueable Apex is to take some set of sObject records, execute some processing such as making a callout to an external REST endpoint or perform some calculations, then update them in the database asynchronously
    • Following code takes a collection of Account records, sets parentId for each record, then updates the records in the database
    • Add the class as a job on the queue, with the second code block below
    • Monitor progress through the Apex Jobs page or by querying AsyncApexJob as shown in the third code block
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;
          // perform other processing or callout
        }
        update accounts;
    }
}
// find all accounts in ‘NY’
List<Account> accounts = [select id from account where billingstate = ‘NY’];
// find a specific parent account for all records
Id parentId = [select id from account where name = 'ACME Corp'][0].Id;
// instantiate a new instance of the Queueable class
UpdateParentAccount updateJob = new UpdateParentAccount(accounts, parentId);
// enqueue the job for processing
ID jobID = System.enqueueJob(updateJob);
SELECT Id, Status, NumberOfErrors FROM AsyncApexJob WHERE Id = :jobID
  • Testing Queueable Apex
    • To ensure that the queueable process runs within the test method, the job is submitted to the queue between the Test.startTest and Test.stopTest block - very similar to Batch Apex testing
@isTest
public class UpdateParentAccountTest {
    @testSetup
    static void setup() {
        List<Account> accounts = new List<Account>();
        // add a parent account
        accounts.add(new Account(name='Parent'));
        // add 100 child accounts
        for (Integer i = 0; i < 100; i++) {
            accounts.add(new Account(
                name='Test Account'+i
            ));
        }
        insert accounts;
    }
    static testmethod void testQueueable() {
        // query for test data to pass to queueable class
        Id parentId = [SELECT id 
                         FROM account
                        WHERE name = 'Parent'][0].Id;
        List<Account> accounts = [SELECT id, name
                                    FROM account 
                                   WHERE name LIKE 'Test Account%'];
        // Create our Queueable instance
        UpdateParentAccount updater = new UpdateParentAccount(accounts, parentId);
        // startTest/stopTest block to force async processes to run
        Test.startTest();
        System.enqueueJob(updater);
        Test.stopTest();
        // Validate the job ran. Check if record have correct parentId now
        System.assertEquals(100, [SELECT count() 
                                    FROM account 
                                   WHERE parentId = :parentId]);
    }
}
  • Chaining Jobs
    • One of the best features in Queueable Apex is job chaining - allows you to run jobs sequentially
    • To chain jobs, submit the second job from the execute() method of your queueable class
    • You can only add one job from an executing job, which means only one child job can exist for each parent job
    • Ex: if you have a second class called SecondJob that implements Queueable, add this class to the queue in the execute() method as follows:
      • Note you can’t chain queueable jobs in an Apex test - check if Apex is running in test context by calling Test.isRunningTest() before chaining jobs
public class FirstJob implements Queueable {
    public void execute(QueueableContext context) {
        // Awesome processing logic here
        // Chain this job to next job by submitting the next job
        System.enqueueJob(new SecondJob());
    }
}
  • Other things to remember
    • Execution of a queued job counts against the shared limit for asynchronous Apex method executions
    • You can add up to 50 jobs to the queue with System.enqueueJob
    • You can only add one job from an executing job with System.enqueueJob, which means that only one child job can exist for each parent queueable job
    • No limit is enforced on the depth of chained jobs (except in Developer Edition and Trial orgs - limit: 5)
public without sharing class AddPrimaryContact implements Queueable {
	
    private Contact contact;
    private String state;
    
    public AddPrimaryContact (Contact inputContact, String inputState) {
		this.Contact = inputContact;
		this.State = inputState;        
    }
    
    public void execute(QueueableContext context) {
        
        // Retrieve 200 Acccount records
        List<Account> accounts = [SELECT Id
                                    FROM Account
                                   WHERE BillingState = :state
                                   LIMIT 200];
        
        // Create empty list of Contact records
        List<Contact> contacts = new List<Contact>();
        
        // Iteraate through the Account records
        for (Account acc : accounts) {
            
            // Clone (copy) the Contact record, make the clone a
            // child of the specific Account record and add to the
            // list of Contacts
            Contact contactClone = contact.clone();
            contactClone.AccountId = acc.Id;
            contacts.add(contactClone);
        }
        
        insert contacts;
    }
}
@isTest
public class AddPrimaryContactTest {
	@isTest
    private static void testQueueableClass() {
        
        // Load test data
        List<Account> accounts = new List<Account>();
        for (Integer i=0; i<500; i++) {
            Account acc = new Account(Name='Test Account');
            if (i<250) {
                acc.BillingState = 'NY';
            } else {
                acc.BillingState = 'CA';
            }
            accounts.add(acc);
        }
        insert accounts;
        
        Contact contact = new Contact(FirstName='Simon',
                                      LastName='Connock');
        insert contact;
        
        // Perform the test
        Test.startTest();
        Id jobId = System.enqueueJob(New AddPrimaryContact(contact, 'CA'));
        Test.stopTest();
        
        // Check the result
        List<Contact> contacts = [SELECT Id
                                    FROM Contact
                                   WHERE Contact.Account.BillingState = 'CA'];
        System.assertEquals(200, contacts.size(),
                            'ERROR: Incorrect number of Contact records found');
    }
}

Schedule Jobs Using the Apex Scheduler

When to use scheduled Apex. How to monitor scheduled jobs. Scheduled Apex syntax. Scheduled method best practices.
  • Scheduled Apex
    • Apex Scheduler lets you delay execution so you can run Apex classes at a specified time
    • Ideal for daily or weekly maintenance tasks using Batch Apex
    • To use the scheduler, write an Apex class that implements the Scheduleable interface
  • Scheduled Apex Syntax
    • To invoke Apex classes to run at specific times, implement the Schedulable interface for the class, then schedule an instance of the class to run at a specific time using System.schedule() method
    • Class implements the Schedulable interface must implement execute() method
    • Parameter of this method is a SchedulableContext object
    • After a class has been scheduled, a CronTrigger object is created that represents the scheduled job - it provides a getTriggerId() method that returns the ID of a CronTrigger API object
public class SomeClass implements Schedulable {
    public void execute(SchedulableContext ctx) {
        // awesome code here
    }
}
  • Sample Code
    • This class queries for open opportunities that should have closed by the current date, and creates a task on each one to remind the owner to update the opportunity
    • You can schedule your class to run either programmatically or from the Apex Scheduler UI
public class RemindOpptyOwners implements Schedulable {
    public void execute(SchedulableContext ctx) {
        List<Opportunity> opptys = [SELECT Id, Name, OwnerId, CloseDate
            FROM Opportunity
            WHERE IsClosed = False AND
            CloseDate < TODAY];
        // Create a task for each opportunity in the list
        TaskUtils.remindOwners(opptys);
    }
}
  • Using the System.Schedule Method
    • After you implement a class with Schedulable, use the System.schedule() method to execute it
    • Use care if you schedule a class from a trigger - need to be able to guarantee the trigger won’t add more scheduled job classes than the limit
    • System.schedule() method takes three arguments: name for the job, CRON expression used to represent the time and date the job will run, and an instance of a class that implements the Schedulable interface
RemindOpptyOwners reminder = new RemindOpptyOwners();
// Seconds Minutes Hours 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);
  • Scheduling a Job from the UI
    • Also possible to schedule a class using the user interface:
      • Setup > Quick Find > Apex > Apex Classes
    • Click Schedule Apex
    • Name the job something like Daily Oppty Reminder
    • Click the lookup button to get a list of all classes that can be scheduled
    • Select Weekly or Monthly for the frequency and set the frequency desired
    • Select start and end dates and a preferred start time
  • Testing Scheduled Apex
    • With Scheduled Apex you must also ensure that the scheduled job is finished bbefore testing against results - use startTest() and stopTest() again around the System.schedule() method to ensure processing finishes before continuing
@isTest
private class RemindOppyOwnersTest {
    // Dummy CRON expression: midnight on March 15.
    // Because this is a test, job executes
    // immediately after Test.stopTest().
    public static String CRON_EXP = '0 0 0 15 3 ? 2022';
    static testmethod void testScheduledJob() {
        // Create some out of date Opportunity records
        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;
        // Get the IDs of the opportunities we just inserted
        Map<Id, Opportunity> opptyMap = new Map<Id, Opportunity>(opptys);
        List<Id> opptyIds = new List<Id>(opptyMap.keySet());
        Test.startTest();
        // Schedule the test job
        String jobId = System.schedule('ScheduledApexTest',
            CRON_EXP,
            new RemindOpptyOwners());
        // Verify the scheduled job has not run yet.
        List<Task> lt = [SELECT Id
            FROM Task
            WHERE WhatId IN :opptyIds];
        System.assertEquals(0, lt.size(), 'Tasks exist before job has run');
        // Stopping the test will run the job synchronously
        Test.stopTest();
        // Now that the scheduled job has executed,
        // check that our tasks were created
        lt = [SELECT Id
            FROM Task
            WHERE WhatId IN :opptyIds];
        System.assertEquals(opptyIds.size(),
            lt.size(),
            'Tasks were not created');
    }
}
  • Things to Remember
    • You can only have 100 scheduled Apex jobs at one time and there are a maximum number of scheduled Apex executions per 24-hour period
    • Use care if you’re planning to schedule a class from a trigger - need to guarantee the trigger won’t add more scheduled jobs than the limit
    • Synchronous Web services callouts are not supported from scheduled Apex - to make callouts, make an aasynchronous callout by placing the callout in a method annotated with @future(callout=true) and call the method from the scheduled Apex
public without sharing class DailyLeadProcessor implements Schedulable {
	
    public void execute(SchedulableContext ctx) {
        // Returns the ID of the CronTrigger schedule
        // System.debug('Context ' + ctx.getTriggerId());
        
        // Get 200 Lead records and modify the LeadSource field
        List<Lead> leads = [SELECT Id, LeadSource
                              FROM Lead
                             WHERE LeadSource = null
                             LIMIT 200];
        for (Lead l : leads) {
            l.LeadSource = 'Dreamforce';
        }
        
        // Update the modified records
        update leads;
    }
    
}
@isTest
public class DailyLeadProcessorTest {
	
    // Midnight every day
    private static string CRON_EXP = '0 0 0 ? * * *'; 
    
    @isTest
    private static void testSchedulableClass() {
    
        // Load test data
        List<Lead> leads = new List<Lead>();
        for (Integer i=0; i<500; i++) {
            if (i<250) {
                leads.add(new Lead(LastName='Connock',
                                   Company='Salesforce'));
            } else {
                leads.add(new Lead(LastName='Connock',
                                   Company='Salesforce',
                                   LeadSource='Other'));
            }
        }
        insert leads;
        
        // Perform the test
        Test.startTest();
        String jobId = System.schedule('Process Leads',
                                       CRON_EXP,
                                       new DailyLeadProcessor());
        Test.stopTest();
    
        // Check the result
        List<Lead> updatedLeads = [SELECT Id, LeadSource
                                     FROM Lead
                                    WHERE LeadSource = 'Dreamforce'];
        System.assertEquals(200, 
                            updatedLeads.size(),
                            'ERROR: At least 1 record not updated correctly');
        
        // Check the scheduled time
        List<CronTrigger> cts = [SELECT Id, TimesTriggered,NextFireTime
                                   FROM CronTrigger
                                  WHERE Id = :jobId];
        System.debug('Next Fire Time ' + cts[0].NextFireTime);
    }
}

Monitor Asynchronous Apex

How to monitor the different types of jobs. How to use the flex queue.
  • Monitoring Asynchronous Jobs
    • Async Jobs work silently in the background, but there are a few ways to monitor jobs:
      • Setup > Quick Find > “Jobs” > Apex Jobs
      • Shows status of all asynchronous Apex jobs with info about each job’s execution as well as their type
    • The Apex Flex Queue also allows you to monitor the status of Apex jobs and reorder them to control which jobs are processed first

  • Monitoring Future Jobs
    • Future jobs show up on the Apex Jobs page like any other jobs
    • Future jobs are not part of the flex queue currently, though
      • You can query AsyncApexJob to find your future job, but you need to filter on some other field like MethodName or JobType to find your job
      • Reference this Stack Exchange post for more info
  • Monitoring Queued Jobs with SOQL
    • Perform a SOQL query on AsyncApexJob by filtering on the Job ID that System.euqueueJob() method returns
AsyncApexJob jobInfo = [SELECT Status, NumberOfErrors
                          FROM AsyncApexJob 
                         WHERE Id = :jobID];
  • Monitoring Queue Jobs with the Flex Queue
    • Apex Flex Queue enables you to submit up to 100 batch jobs for execution
    • Any jobs that are submitted for execution are in holding status and placed in the Flex queue
    • Jobs are processed first-in, first-out
    • You can shuffle the order
    • As system resources become available, the system takes the next job from the top of the Apex Flex queue and moves it to the batch job queue
      • System can process up to five queued or active jobs simultaneously per organization
      • Status of those moved jobs changes from Holding to Queued
  • Monitoring Scheduled Jobs
    • After an Apex job has been scheduled, you can find more info about it by querying CronTrigger
    • Following includes number of times the job has run, and the date and time when the job will run again
    • Uses a jobID variable which is returned from System.schedule() method
CronTrigger ct = [SELECT TimesTriggered, NextFireTime 
                    FROM CronTrigger 
                   WHERE Id = :jobID];
  • If you’re performing the query inside the execute method of your schedulable class, you can obtain the ID of the current job by calling getTriggerId() on the SchedulableContext argument variable
public class DoAwesomeStuff implements Schedulable {
    public void execute(SchedulableContext sc) {
        // some awesome code
        CronTrigger ct = [SELECT TimesTriggered, NextFireTime 
                            FROM CronTrigger 
                           WHERE Id = :sc.getTriggerId()];
    }
}
  • You can also get the job’s name and type from the CronJobDetail record associated with the CronTrigger record
    • Use the CronJobDetail relationship when performing the query.
CronTrigger job = [SELECT Id, CronJobDetail.Id, CronJobDetail.Name, 
                          CronJobDetail.JobType 
                     FROM CronTrigger 
                    ORDER BY CreatedDate DESC 
                    LIMIT 1];
  • Alternatively, query CronJobDetail directly to get the job’s name and type
CronJobDetail ctd = [SELECT Id, Name, JobType 
                       FROM CronJobDetail 
                      WHERE Id = :job.CronJobDetail.Id];
  • Lastly, get the total count of all Apex scheduled jobs excluding all other scheduled job types, perform the following
    • Value 7 is specified for the job type, which corresponds to the scheduled Apex job type
    • See CronJobDetail for more detail

  • Future Method jobs do not show up in the Apex Flex queue
  • The Flex Queue is processed first-in first-out