Stay updated

Let’s see how the API of Quartz 3.0 has changed and how to stop asynchronous Jobs
Stopping asynchronous Jobs of Quartz 3
Wednesday, November 20, 2019

During the common refactoring on a client’s project, we converted the previous synchronous code in an asynchronous version, using the Tasks and the pattern async-await of the .NET framework. I suggest that you read the article about this issue, written by my colleague Francesco Vastarella.

One of the reasons for this request was the wrong answer of the application to the shutdown requests, and the resulting failure of firsts deploy attempts of new versions.
Searching for the reasons for this slow shutdown, we realize some operations remain in “waiting” after the shutdown request. We decided then to use the CancellationToken of the .Net Framework to manage the Job stop requests.

The first step is the update of the Quartz library. For those who don’t know it, Quartz is a library that permits to manage scheduled jobs, that run in background. The problem is easily solved if you use .NET Core in your application, thanks to the Hosted Services.

Quartz introduces the possibility of managing jobs asynchronously in the 3.0 version. If you still don’t know the library in detail, a Job it’s only a class that implements the IJob interface

namespace Quartz    
    public interface IJob       
        Task Execute(JobExecutionContext context);

If you have already used the Quartz 2.0 version, you surely remember that to stop a Job, the Job itself must implement the interface IInterruptableJob.

public interface IInterruptableJob      
    void Interrupt();

The implementation of a Job stop was free, and it happens to me to see the use of Thread.Abort(). It’s better to avoid this approach because there is the risk of leaving the application in a “not managed” state. This method only lifts the exception of a ThreadAbortException type (which cannot be suppressed with a catch because it is automatically relaunched), which stops the running thread immediately.

This method could activate a series of problems. For example, having an IDisposable object on which it is impossible to call Dispose because it is present in a finally block, that has not been reached.

With the new version of Quarts this interface disappears. In this version, the stop of a Job is requested using a Scheduler, the main API of the library that manage the execution and the scheduling of the Job.

var scheduler = await factory.GetScheduler();
await scheduler.Start();
IJobDetail job = JobBuilder.Create<hellojob>()  
    .WithIdentity("myJob", "group1")    
ITrigger trigger = TriggerBuilder.Create()  
    .WithIdentity("myTrigger", "group1")  
    .WithSimpleSchedule(x => 
        x .WithRepeatCount(1).WithIntervalInSeconds(40)).Build();
await scheduler.ScheduleJob(job, trigger);
// Configure the cancellation of the schedule job with jobkey
await Task.Delay(TimeSpan.FromSeconds(1));
await scheduler.Interrupt(job.Key);

If you have experience with asynchronous programming, you noted that in this example, the CancellationToken lacks. How can we use one to delete other eventual Tasks called from our Job?

The pattern used to use the CancellationToken in the .NET framework consists of 4 steps:

  • Instantiate a CancellationTokenSource object, that manages and send deletion notifications to single Token
  • Passing the token returned from the CancellationTokenSource.Token property to any Task you want to manage the deletion of
  • Arrange a system to make any Task answering to the deletion
  • Recall the CancellationToken.Cancel method to send a deletion request.

This pattern is implemented from the Quartz library inside the context of the Job . The invocation of the interrupt from the scheduler only calls a Cancel() of the context of the Job that, in turn, recall the Cancel() of the instantiated CancellationTokenSource. You can observe this system in the source code of the library on GitHub: https://github.com/quartznet/quartznet/blob/master/src/Quartz/Core/QuartzScheduler.cs.

At this point all that remains is to propagate the cancellation token of the Job to all the Tasks of which we want to manage the cancellation:

public async Task Execute(IJobExecutionContext context) {
    await MyAsyncTask(context.CancellationToken);  

I hope you have been inspired to use this information in your projects.

See you in the next article!