Scheduled Tasks Using Quartz and AppHarbor Background Workers
In our last post about background workers we talked about simple one-time background task execution and how to handle errors and capture output from them. This post will build on the concepts introduced in that post and introduce how to use Quartz to run tasks on a schedule.
Quartz: "Enterprise" Job Scheduler for .NET
Quartz is a job scheduling framework that is a port from the popular Java version of the same name. While the syntax can feel a little Java-ish, the just-released version 2.0 has made many improvements. The code is available on GitHub and documentation can be found on the project site. Note that at the time of publishing this post the Quartz tutorial has not yet been updated to use the new version of the library.
Here are some example use cases that are supported:
- Run a job one time at a specified time
- Run a job every 10 seconds
- Run a job every day at a specified time of day
- Run a job using a cron-compatible schedule
For more examples, head over to the project site.
There are three pieces involved in scheduling a task to run. The first part is the job itself. Jobs are simple classes that implement the one required method of IJob
called Execute
. The next piece is called a trigger. Triggers are containers for scheduling logic that allow you to specify when the job should be run. Triggers can be configured with a variety of different schedules and intervals.
After you create a job and a trigger you pass a pair of them to a scheduler which takes care of running them at the appointed times.
Your First Scheduled Task
For our first example we'll create a worker that outputs the current time to the console every 15 seconds. Start by creating a new console app in Visual Studio and installing the Quartz package via NuGet.
The logic for our task is a simple implementation of IJob:
using System;
using Quartz;
namespace QuartzIntro
{
public class SampleJob : IJob
{
public void Execute(IJobExecutionContext context)
{
Console.WriteLine(DateTimeOffset.Now);
}
}
}
In a more practical use case you can replace our useless implementation with any code that you'd like. With the job logic done, we can create a trigger and schedule the job:
using Quartz;
using Quartz.Impl;
namespace QuartzIntro
{
class Program
{
static void Main(string[] args)
{
// construct a scheduler
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
var job = JobBuilder.Create<SampleJob>().Build();
var trigger = TriggerBuilder.Create()
.WithSimpleSchedule(x => x.WithIntervalInSeconds(15).RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
}
There are a few interesting things worth pointing out here. First, a scheduler can handle any number of jobs and triggers. Second, as long as the scheduler has jobs scheduled the application will continue to run. That means you don't have to put in any while (true)
blocks to keep it alive. If the app exits with a non-zero code (which most likely means it encountered an unhandled exception), AppHarbor will restart it and the scheduler will resume. If the scheduler doesn't have any outstanding tasks the application will exit and restart on the next deploy.
Complex Schedules
Quartz supports cron-style scheduling as well to suit your most intricate scheduling needs. For instance, if we have a task that needs to be run at 10:30, 11:30, 12:30, and 13:30 on every Wednesday and Friday we can create a trigger with that schedule using the cron syntax. We'll also tell it to start an hour after the worker is launched and to end in seven days.
using System;
using Quartz;
using Quartz.Impl;
namespace QuartzIntro
{
class Program
{
static void Main(string[] args)
{
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
var job = JobBuilder.Create<SampleJob>().Build();
var cron = TriggerBuilder.Create()
.WithCronSchedule("0 30 10-13 ? * WED,FRI")
.StartAt(DateBuilder.FutureDate(1, IntervalUnit.Hour))
.EndAt(DateTimeOffset.Now.AddDays(7))
.Build();
scheduler.ScheduleJob(job, cron);
}
}
}
Just Scratching the Surface
We've only just touched on a small amount of the functionality Quartz offers. Try it out in your next AppHarbor background worker to see if it will work for your scheduling needs.