Using Quartz.net
Quartz can be used as Hosted in your application. We can also use it as a separate process including hosted in a Windows Service. For our example we can use it as a scheduler hosted in our simple console application. Quartz is available as a Nuget package. You can use it when you want to host the scheduler in your application. You can also download the libraries package from Sourceforge.net.

Main Scheduling API Objects
If we take a moment to think about a scheduling API, there should be a main Scheduler which should be used to build and execute schedules. Each schedule should be a job executed based on a certain pre-configured trigger mechanism. So there are three types of objects. They are Scheduler, Job and Triggers. Quartz also follow the same conceptual model.

Quartz uses Triggers for "When to do" and Jobs for "What to do" needs". We also need a scheduler to register these jobs and triggers. The triggers would notify the scheduler when they are activated, which results in execution of jobs by the scheduler. There might be more than one jobs executed in a schedule. Similarly, we might need to execute the same job as a result of activation of more than one triggers.

Quartz Jobs
As we discussed above, a job is simply an operation that we need to execute when a trigger is saturated. Quartz Job's interface is just IJob interface which just defines a contract for Execute operation. The jobs might be interrupt-able.

Following is a simple job which just prints the firing time on the console. It implements IJob interface. The firing time can be obtained from IJobExecutionContext passed as argument to Execute method.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal class MessageJob : IJob | |
{ | |
public void Execute(IJobExecutionContext context) | |
{ | |
Console.WriteLine("Fired @ {0}", context.FireTimeUtc); | |
} | |
} |

Quartz Triggers
The scheduled job must be triggered based on some triggering event. All Quartz trigger interfaces are based on ITrigger.

The base trigger interface is inherited by a number of child interfaces providing more meaningful triggering details.

These interfaces are implemented by concrete classes in System.Impl.Triggers namespace. Here AbstractTrigger is the base class of these triggers. The available triggers are ConTriggerImpl, SimpleTriggerImpl, DailyTimeIntervalTriggerImpl and CalendarIntervalTriggerImpl.

Creating Schedules
As we discussed above, Triggers and Jobs can be used to create schedules. They can be registered with a Scheduler. Here Scheduler works as a Mediator which executes a Job based on saturation of a trigger's event. Job are specified using JobDetailImpl. This is a momento which would be used for persistence of jobs and their respective schedules. Here we have used an instance of MessageJob, defined above, and used it with CalendarIntervalTriggerImpl to create a schedule using Scheduler. As per Quartz documentation, every time a scheduler needs to execute the job, it creates a new instance of IJob. It then garbage collects the object later on.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var schedulerFactory = new StdSchedulerFactory(); | |
IScheduler scheduler = schedulerFactory.GetScheduler(); | |
var jobDetails = new JobDetailImpl("MessageJob", typeof (MessageJob)); | |
var trigger = new CalendarIntervalTriggerImpl("myTrigger", IntervalUnit.Second, 3); | |
scheduler.ScheduleJob(jobDetails, trigger); | |
scheduler.Start(); |

Calendars for Trigger's Exclusion List
Quartz provides a comprehensive list of calendars. These calendars allows us to provide exclusion list. All of them implement ICalendar through inheriting BaseCalendar. A calendar is associated with a trigger to provide exclusion list the the trigger inclusion list. A Calendar can be based on another ICalendar through CalendarBase property, which allows us to specify more sophisticated exclusion lists. It is easy to check if a given time is part of exclusion list through IsTimeInluded method of ICalendar. We can also get the next scheduled time after given time. In this case it just returns the next scheduled time if it is not in the exclusion list.

All the required calendars must be registered with the scheduler. A Trigger specifies the name of the calendar registered by the scheduler to provide the exclusion list. In the below code, we are just adding the calendar details to the trigger to include the exclusion details. The we are registering the actual calendar object with the key name with the scheduler. Here a DailyCalendar is use with start and ending time specified in 24-hour format.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var schedulerFactory = new StdSchedulerFactory(); | |
IScheduler scheduler = schedulerFactory.GetScheduler(); | |
var jobDetails = new JobDetailImpl("MessageJob", typeof(MessageJob)); | |
var calendar = new DailyCalendar("12:00:00", "13:00:00"); | |
var trigger = | |
new CalendarIntervalTriggerImpl("myTrigger", IntervalUnit.Second, 3) | |
{ | |
CalendarName = "exclusionCalendar" | |
}; | |
scheduler.AddCalendar("exclusionCalendar", calendar, true, true); | |
scheduler.ScheduleJob(jobDetails, trigger); | |
scheduler.Start(); |
Passing Data for Job Execution
As we discussed above, the scheduler uses JobFactory to create an instance of the IJob implementation. So we cannot directly instantiate the members of the job. Quartz provides JobDataMap for just that purpose. We can add key / value pairs to the JobDataMap associated with the JobDetail or Trigger. The JobDataMap values are assigned to the members of job after instantiation if key is same as one of the properties of Job. In the following example we are adding the JobDataMap to a JobDetail and Trigger. It must be remembered that multiple job details might use the same job with different JobDataMap, which can be used in other schedules.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var schedulerFactory = new StdSchedulerFactory(); | |
IScheduler scheduler = schedulerFactory.GetScheduler(); | |
var jobDetails = new JobDetailImpl("MessageJob", typeof (MessageJob)); | |
jobDetails.JobDataMap.Add("MyKey", "MyValue"); | |
var calendar = new DailyCalendar("12:00:00", "13:00:00"); | |
var trigger = | |
new SimpleTriggerImpl("myTrigger", 1, TimeSpan.FromSeconds(10)) | |
{ | |
CalendarName = "exclusionCalendar" | |
}; | |
trigger.JobDataMap.Add("MyTriggerKey", "My Trigger Value"); | |
scheduler.AddCalendar("exclusionCalendar", calendar, true, true); | |
scheduler.ScheduleJob(jobDetails, trigger); | |
scheduler.Start(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[PersistJobDataAfterExecution, DisallowConcurrentExecution] | |
internal class MessageJob : IJob | |
{ | |
public void Execute(IJobExecutionContext context) | |
{ | |
Console.WriteLine("Fired @ {0}", context.FireTimeUtc); | |
Console.WriteLine("MyKey: {0}", MyKey); | |
Console.WriteLine("MyTriggerKey: {0}", MyTriggerKey); | |
if (!context.MergedJobDataMap.Contains("MyNewVal")) | |
{ | |
context.JobDetail.JobDataMap.Add("MyNewVal", "MyVal"); | |
Console.WriteLine("ValueAdded"); | |
} | |
Console.WriteLine( | |
"JobDetails DataCount: {0}, Trigger DataCount:{1}, Merged Count: {2}", | |
context.JobDetail.JobDataMap.Count , | |
context.Trigger.JobDataMap.Count, | |
context.MergedJobDataMap.Count); | |
} | |
public string MyKey { get; set; } | |
public string MyTriggerKey { get; set; } | |
} |
The above code would result in the following output. Please notice how JobDataMap values are injected into the properties.

Trigger Set for One Job
Quartz Scheduler allows the same JobDetails to be scheduled with multiple trigger. In the following code we are adding a SimpleTriggerImp and CronTriggerImpl. We are creating a JobDetails for MessageJob and then creating a schedule. Here CronTriggerImpl allows us to schedule based on the Cron expression as in Unix's Cron scheduler.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var schedulerFactory = new StdSchedulerFactory(); | |
IScheduler scheduler = schedulerFactory.GetScheduler(); | |
var jobDetails = new JobDetailImpl("MessageJob", typeof(MessageJob)); | |
jobDetails.JobDataMap.Add("MyKey", "MyValue"); | |
var calendar = new DailyCalendar("12:00:00", "13:00:00"); | |
var trigger = | |
new SimpleTriggerImpl("myTrigger", 0, TimeSpan.FromSeconds(10)) | |
{ | |
CalendarName = "exclusionCalendar" | |
}; | |
var trigger2 = | |
new CronTriggerImpl("myTriger", "Group1", "0 3 19 ? * *") | |
{ | |
CalendarName = "exclusionCalendar" | |
}; | |
trigger.JobDataMap.Add("MyTriggerKey", "My Trigger Value"); | |
var set = new Quartz.Collection.HashSet<ITrigger> {trigger, trigger2}; | |
scheduler.AddCalendar("exclusionCalendar", calendar, true, true); | |
scheduler.ScheduleJob(jobDetails, set, true); | |
scheduler.Start(); |
Here ISet<T> is the interface type provided by Quartz. The implementations including TreeSet<T>, HashSet<T> and ReadOnlySetISet<T>.

Quartz Listeners
Quartz provides listeners to allow execution of actions based on certain events on job, schedule and trigger. There are three types of interfaces IScheduleListener, ITriggerListener and IJobListener for listening of events on schedule, triggers and jobs. There are abstract implementation of these interfaces including TriggerListenerSupport, ScheduleListenerSupport and JobListenerSupport. These abstract implementations are mostly for providing convenience to implementers of the listener interfaces with protected constructors and virtual members. This makes it easier as we just need to override the required members.

There are also BroadCast implementations of these interfaces including BroadCastJobListener, BroadCastSchedulerListener and BroadcastTriggerListener. This is based on Composite pattern where multiple listeners can be added to these broadcast listener's instance. We just need to discuss an instance of this broadcast listener with scheduler instead of requiring the individual listeners' registrations.

In the following example, we are using JobListnerSupport to create a custom Job listener. Here we just want to print the job details to console after they are executed by overriding JobExecuted method.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal class MessageJobListener : JobListenerSupport | |
{ | |
public override string Name | |
{ | |
get { return "MessageJobListener"; } | |
} | |
public override void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException) | |
{ | |
base.JobWasExecuted(context, jobException); | |
Console.WriteLine("Job Executed: {0}", context.JobDetail.Key); | |
} | |
} |
All listeners must be added to scheduler's ListenerManager. It includes, job, trigger and scheduler listeners.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var schedulerFactory = new StdSchedulerFactory(); | |
IScheduler scheduler = schedulerFactory.GetScheduler(); | |
var jobDetails = new JobDetailImpl("MessageJob", typeof (MessageJob)); | |
var trigger = new SimpleTriggerImpl("myTrigger", 1, TimeSpan.FromSeconds(10)); | |
var jobListener = new MessageJobListener(); | |
var jobKey = new JobKey("MessageJob"); | |
scheduler.ListenerManager.AddJobListener(jobListener, KeyMatcher<JobKey>.KeyEquals(jobKey)); | |
scheduler.ScheduleJob(jobDetails, trigger); | |
scheduler.Start(); |
The above code would result in the following output:

Quartz Builder API
In order to make developer's lives easier, Quartz provides several builder interfaces to create schedules, triggers and jobs. In the following code, we are using TriggerBuilder, JobBuilder and ScheduleBuilderto create a schedule. These are fluent interfaces. Look at how easy it has become to create a trigger, assign job details to it, specify schedule using a schedule builder, provide exclusions using calendar. Since a job has already been assigned to the trigger, we don't need to specify it again for creating a schedule. We need to add the job details to the scheduler separately.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var jobDetails = JobBuilder | |
.Create<MessageJob>() | |
.WithIdentity(new JobKey("MessageJob")) | |
.Build(); | |
var trigger = TriggerBuilder | |
.Create() | |
.WithIdentity(new TriggerKey("myTrigger")) | |
.WithSchedule(SimpleScheduleBuilder.Create().WithRepeatCount(2).WithIntervalInSeconds(10)) | |
.ModifiedByCalendar("exclusionCalendar") | |
.ForJob(jobDetails) | |
.Build(); | |
var scheduler = new StdSchedulerFactory().GetScheduler(); | |
scheduler.AddCalendar( | |
"exclusionCalendar", | |
new DailyCalendar("12:27:00", "12:28:00"), | |
true, | |
true); | |
scheduler.ListenerManager.AddJobListener( | |
new MessageJobListener(), | |
KeyMatcher<JobKey>.KeyEquals(new JobKey("MessageJob"))); | |
scheduler.AddJob(jobDetails, true); | |
scheduler.ScheduleJob(trigger); | |
scheduler.Start(); |
In the above example, we used SimpleScheduleBuilder. There are also additional scheduler builders available to create the scheduler specific to your needs.

Quartz Exceptions
It is OK to create a job with no associated trigger assigned. The job details would just be dormant in the scheduler. But Scheduler throws SchedulerException if we add a trigger which would never get fired.

Quartz also provides other exception types. The hierarchy is as follows:

Further Readings:
4 comments:
Absolute genius of a post - Thank you.
One small point and it may be becase I am using a later version of quartz,but, I couldn't set a job without a trigger unless I also set StoreDurably. eg:
var jobDetails = JobBuilder .Create().StoreDurably.WithIdentity(new JobKey("MessageJob")) .Build();
Thanks Ken. Which version are you using?
Excellent article! Thank you!
Great article, specially diagrammatic representation.
Post a Comment