Implement interruptible tasks based on Quartz.NET
Quartz.NET is an open source job scheduling framework that is well suited for routine work, periodic polling for database synchronization, periodic mail notifications, periodic processing of data, and so on.Quartz.NET allows developers to schedule jobs based on time intervals (or days).It implements a many-to-many relationship between jobs and triggers and associates multiple jobs with different triggers.Applications that integrate Quartz.NET can reuse jobs from different events or combine multiple jobs for one event.Worker is not a background thread (IsBackground= false) in the default implementation of Quartz.NET, so when we terminate the scheduler (invoke the Scheduler.Shutdown() method), if a more time-consuming Job is executing, the process will not end immediately, but wait for the Job to finish executing and end again.
In order to exit the process immediately, we need to know one of the built-in interfaces in Quartz.NET: IInterruptableJob.The interface represents an interruptable task, and the Job implementing the interface is considered to be executable by interruption. The following is the official definition and explanation of the IInterruptable Job interface:
The interface to be implemented by Quartz.IJobs that provide a mechanism for having their execution interrupted. It is NOT a requirement for jobs to implement this interface - in fact, for most people, none of their jobs will. The means of actually interrupting the Job must be implemented within the Quartz.IJob itself (the Quartz.IInterruptableJob.Interrupt method of this interface is simply a means for the scheduler to inform the Quartz.IJob that a request has been made for it to be interrupted). The mechanism that your jobs use to interrupt themselves might vary between implementations. However the principle idea in any implementation should be to have the body of the job's Quartz.IJob.Execute(Quartz.IJobExecutionContext) periodically check some flag to see if an interruption has been requested, and if the flag is set, somehow abort the performance of the rest of the job's work. An example of interrupting a job can be found in the source for the class Example7's DumbInterruptableJob It is legal to use some combination of System.Threading.Monitor.Wait(System.Object) and System.Threading.Monitor.Pulse(System.Object) synchronization within System.Threading.Thread.Interrupt and Quartz.IJob.Execute(Quartz.IJobExecutionContext) in order to have the System.Threading.Thread.Interrupt method block until the Quartz.IJob.Execute(Quartz.IJobExecutionContext) signals that it has noticed the set flag. If the Job performs some form of blocking I/O or similar functions, you may want to consider having the Quartz.IJob.Execute(Quartz.IJobExecutionContext) method store a reference to the calling System.Threading.Thread as a member variable. Then the implementation of this interfaces System.Threading.Thread.Interrupt method can call System.Threading.Thread.Interrupt on that Thread. Before attempting this, make sure that you fully understand what System.Threading.Thread.Interrupt does and doesn't do. Also make sure that you clear the Job's member reference to the Thread when the Execute(..) method exits (preferably in a finally block).
This interface defines an Interrupt method that Quartz.IScheduler calls when the Scheduler.Shutdown() method is called to interrupt a running task.This means that we need to implement our own interrupt method to stop the current Job.Usually, we can terminate a task by taking the current working thread and calling the Thread Abort method on interruption.
public class CommonInterruptableJob : IInterruptableJob { private Thread _currentThread; public void Execute(IJobExecutionContext context) { _currentThread = Thread.CurrentThread; try { //TODO: Write your task code } finally { _currentThread = null; } } public void Interrupt() { if (_currentThread != null) { _currentThread.Abort(); _currentThread = null; } } }
This method is simple and rough, and performs satisfactorily under some less stringent conditions.A more elegant way is to define a Boolean field _stop, which defaults to false and is set to true when the Interrupt method is called.Constantly detect the value of this field when Execute and exit processing at the appropriate time.
public class NiceInterruptableJob : IInterruptableJob { private bool _stop; public void Execute(IJobExecutionContext context) { //Assume my task is to cycle 1000 times to process some data for (var i = 0; !_stop && i < 1000; i++) { //TODO: Processing code } } public void Interrupt() { _stop = true; } }
This article gives a brief introduction to Quartz.NET and shows two different ways to accomplish task termination.The first method is simple and rough, simple to write, suitable for most occasions where the requirements are not too strict.Mode two is more elegant, more accurate and less dangerous, but more complex to write.