4

So I am looking to implement Queueable jobs in apex to send 200 emails (1 job) every 30 minutes until all the jobs are done, I am struggling to find any documentation on how this functionality can be added so I figured I would ask the community, if anyone has ever implemented time delays between Queueable jobs.

  • One thing you can do is before calling queueable job soql query on apexasynjob to see how many jobs are still pending or queued – sf_user Jan 26 '17 at 12:50
  • 1
    try to use scheduled jobs instead of queueable – kurunve Jan 26 '17 at 12:55
  • Based on the new question that you've posted recently, community etiquette is to help others by marking your question as solved through checking the answer that resolved your issue. If you don't, it leaves the impression to anyone who searches the topic later that your question wasn't answered to your satisfaction. Upvoting is also encouraged, which gives other readers some idea as to the quality of questions/answers. – Derek F Jan 26 '17 at 16:37
  • sorry derek forgot will do now – Nebbyyy Jan 26 '17 at 16:45
5

This is from Dan Appleman's Advanced Apex Programming Ver 3. The code is available for download from his web site at http://www.AdvancedApex.com

This pattern uses a dispatcher class that implements Schedulable to enque queueables like what you have at whatever interval you want to configure using the chron trigger object. Here's the essence of the code you'd need to use.

The class below is used to control and schedule your queables, acting as a dispatcher.

public class GoingAsync5 
    implements ScheduledDispatcher2.IScheduleDispatched {

    public void execute(SchedulableContext sc)
    {
        // When used as a backup to start the asnyc framework
        system.enqueueJob(new GoingAsync4());
        // Always abort the job on completion
        system.abortJob(sc.getTriggerID()); 
    } 

    public static String getSchedulerExpression(Datetime dt) {
        // Don't try to schedule Apex before current time + buffer
        if(dt < DateTime.Now().AddMinutes(1)) 
            dt = DateTime.Now().AddMinutes(1);
        return ('' + dt.second() + ' ' + dt.minute() + ' ' + 
            dt.hour() + ' ' + dt.day() + ' ' + 
            dt.month() + ' ? ' + dt.year());
    }

    public static void startScheduler(DateTime scheduledTime, String jobName)
    {

        // Is the job already running?
        List<CronTrigger> jobs = 
            [SELECT Id, CronJobDetail.Name, State, NextFireTime
             FROM CronTrigger 
             WHERE CronJobDetail.Name= :jobName];
        if(jobs.size()>0 && jobs[0].state!='COMPLETED' && 
           jobs[0].state!='ERROR' && jobs[0].state!='DELETED')
        {
            // It's already running/scheduled

            // Depending on your design you might want to exit,
            // or abort and reschedule if the requested start time 
            // is earlier
            return;            
        }

        // If the job exists, it needs to be deleted
        if(jobs.size()>0) system.abortJob(jobs[0].id);


        try
        {
            System.schedule(jobName, 
                            getSchedulerExpression(scheduledTime), 
                            new ScheduledDispatcher2());
        } catch(Exception ex)
        {
            system.Debug(ex.getMessage());
            // Log the error?
            // Or throw the error to the caller?
        }
    }
}

Methods used in class to call dispatcher

// method used to schedule the next job using the above scheduler queuable

public static void enqueueGoingAsync4(ID currentJobId)
{
    List<AsyncApexJob> jobs = [Select ID, Status, ExtendedStatus from AsyncApexJob 
                where JobType = 'Queueable' And (status='Queued'  Or Status='Holding') 
                and CreatedById = :userinfo.getUserID() and 
                ApexClass.Name='GoingAsync4' and ID!= :currentJobId Limit 1 ];
    if(jobs.size()==1) return;  // Already have one queued that isn't this one.

    system.enqueueJob(new GoingAsync4());
}


@future
private static void tryToQueue()
{
    // below if statement checks to see if the class that disables the queueables has been turned off.
    // if enabled, check limits class first, then call enqueue method

    if(!AppConfigSupport.appEnabled) return; // On/off switch
    try
    {
        if(Limits.getLimitQueueableJobs() - Limits.getQueueableJobs() > 0)
            enqueueGoingAsync4(null);
    }
    catch(Exception ex)
    {
        // Wait for someone else to make a request...
        // Or maybe use scheduled Apex?
    }
}

The base class for the Dispatcher

global class ScheduledDispatcher2 Implements Schedulable {

    public Interface IScheduleDispatched 
    { 
        void execute(SchedulableContext sc); 
    } 

    global void execute(SchedulableContext sc) 
    {
        Type targetType = Type.forName('GoingAsync5');   
        if(targetType!=null) {
            IScheduleDispatched obj = 
                (IScheduleDispatched)targettype.newInstance();
            obj.execute(sc);   
        } 
    } 
}
  • Really good read, thanks for recommending the book I will be be purchasing it for sure – Nebbyyy Jan 26 '17 at 15:50
4

It's easier with Batchable, which allows you to call:

public static String scheduleBatch(Database.Batchable batchable, String jobName, Integer minutesFromNow)

In theory, you can use schedules and "suicide scheduling", but I find this approach much easier to use.

public static Boolean chainBatches = true; 
public void finish(Database.BatchableContext context)
{
    if (chainBatches) System.scheduleBatch(this, 'MyClass Job ' + Datetime.now().format(), 30);
}
  • Hi Adrian I had previously attempted this but all it seemed to do was schedule the whole batch (all emails) to 30 minutes in the future then run it. Is there any way to limit how much can be in each batch e.g. batch 4000, if more left batch 4000 more in 24 hours ect – Nebbyyy Jan 26 '17 at 14:20
  • You have to modify your query to work with this paradigm. You can add a LIMIT clause and find a way to filter out records already processed. – Adrian Larson Jan 26 '17 at 14:25
3

The flex queue (queueable) has no functionality to specify any delay. In that regard, it's exactly like @future. It can only put off the execution of your code until an unspecified (but usually very near) point in the future.

If you need a delay, then you need to use scheduled apex. The documentation on Scheduled Apex contains some good examples that should get you going.

Some considerations to make:

  • You cannot modify a class if it has been scheduled to run
  • The above can interfere with deployments, you may need to enable the 'Deploy with active jobs' feature discussed in the Winter '15 release notes

The format of the string used to tell the scheduler when to run your code is very similar to that of the 'cron' utility in unix/linux. To run once every 30 minutes, you'll need to schedule 2 jobs (one to run, say, on the hour, and a second to run on the half hour).

An example of the two schedule strings that you might be using is:

0 5 * ? * MON-FRI and 0 35 * ? * MON-FRI

which translated means

run at 0 seconds and 5 minutes past every (* = wildcard) hour, with no restriction on the day of month (? = not specified), during every month, if it's between Monday and Friday (inclusive)

and

run at 0 seconds and 35 minutes past every hour, with no restriction on the day of month, during every month, if it's between Monday and Friday (inclusive)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.