Asynchronous Job Processing
Using Quartz.Net
Jay Vilalta jay.vilalta@gmail.com
What Is Quartz.Net
• Scheduler (think task scheduler)
• Queue for asynchronous jobs
• C# port of Quartz (java)
• Apache license
Why Use Quartz.Net
• Scale out
• Redundancy
• Smart handling of failures
• Job chaining (poor man’s workflow)
• Custom scheduling
How Can I Run It
• Embedded in your application
• As a stand alone windows service
• Scheduler
• Jobs
• Triggers
The Basics
Scheduler
• Runs jobs
• Manages the scheduling
• Do the work
• Some built-in
• Mostly roll you own
• Implement IJob
Jobs
Built-in Jobs
• FileScanJob: monitors last modified date
• NativeJob: runs executables or batch files
• NoOpJob: does nothing
• SendMailJob: sends emails
Jobs - Example
} public class MyJob : IJob { public void Execute(JobExecutionContext context) { try { int count= context.MergedJobDataMap.GetIntegerFromString(“count"); for (int i = 0; i < count; i++) {
//do something useful
}
}
} catch (ApplicationException ex) { throw new JobExecutionException("Something happened", ex, false);
}
Triggers
• Tell the scheduler when jobs should run
• Some built-in
– Simple Trigger
– Cron Trigger
– NthIncludedDayTrigger
• Custom Triggers
Simple Trigger
• Start Time
• Repeat Count
• Repeat Interval
Cron Trigger
• Similar to UNIX cron
• Start Time
• Cron Expression
– “0 15 10 ? * *”
– “0 0,15,30,45 * ? * *”
Custom Triggers
• No example this time
• Implementing a trigger is not trivial
• Must implement 11 methods
• Must be able to determine next fire time
Scheduling
• Associate a job to a trigger
• Multiple triggers can be set
• When the trigger fires, the job runs scheduler.ScheduleJob(jobDetail, trigger);
Advanced Features
• Listeners
• Special Jobs
• Remote management
• Clustering
• Plug-ins
• Unit testing
Listeners
• Job Listeners
• Trigger Listeners
• Scheduler Listeners
• Job and trigger listeners can be global
Job Listener Example
}
{ public class JobHistoryListener : IJobListener public void JobExecutionVetoed(…) public void JobToBeExecuted(…) public void JobWasExecuted(…)
Trigger Listener Example
}
{ public class MyTriggerListener:ITriggerListener public string Name public void TriggerComplete(…) public void TriggerFired(…) public void TriggerMisfired(…) public bool VetoJobExecution(…)
Special Jobs
• Stateful Jobs
• Interruptible Jobs
Stateful Jobs
• Only one can run at a time
• Allow you to save/restore state
• You must manage state yourself
• Implement IStatefulJob
Interruptible Jobs
• Mechanism to interrupt long running jobs
• You must implement yourself
• Implement IInterruptableJob
Remote Management
• Scheduler can be managed remotely
• Exposed via Remoting
• Most scheduler functions available
JobFactory
• Instantiates jobs
• Default factory creates a new instance
• Create your own if you use DI or IoC container
• RAMJobStore
• AdoJobStore
– MySql
– Oracle
– Postgres
– SQL Lite
– SQL Server
Job Stores
Clustering
• Load balancing
• Job Failover
• Caveat: clocks synchronized within a second
Plug-ins
• JobInitializationPlugin
• LoggingJobHistoryPlugin
• LoggingTriggerHistoryPlugin
• ShutdownHookPlugin
Plug-ins Stub public class SamplePlugin : ISchedulerPlugin
{ public void Initialize(string name, IScheduler sched) public void Shutdown() public void Start()
}
Unit Testing
• You can / should unit test your quartz classes
• Use a mocking framework
• Mock the Scheduler (IScheduler)
• Mock a calendar (ICalendar)
• Use mocks to create your context
Sample Unit Test
[Test] public void ExecuteTests()
{
JobDetail detail = new JobDetail();
IScheduler scheduler = new Mock<IScheduler>().Object;
ICalendar calendar = new Mock<ICalendar>().Object;
IJob job = new NoOpJob(); detail.Name = "Test"; detail.JobDataMap.Add("SOMETHING", "ELSE");
TriggerFiredBundle bundle = new TriggerFiredBundle(detail, new SimpleTrigger(), calendar, false, null, null, null, null);
JobExecutionContext context = new JobExecutionContext(scheduler, bundle, job);
JobHistoryListener listener = new JobHistoryListener(); listener.JobToBeExecuted(context); listener.JobWasExecuted(context, null);
//methods return void so need to get creative to determine if execution was successful
}
}
Resources
Project Home: http://quartznet.sourceforge.net/
Mailing List: http://groups.google.com/group/quartznet
Getting Started: http://jvilalta.blogspot.com