/ quartz

Introduction to Quartz.NET

This will be a first of a three part series on Quartz.NET. I will be explaining how my team at work implemented a durable workflow system with Quartz.NET! This first post will serve as a simple overview of Quartz.NET and how to get started using it.

So what is Quartz.NET? From the Quartz.NET website itself:

Quartz.NET is a pure .NET library written in C# and is a port of very popular open source Java job scheduling framework, Quartz.

A Job Scheduler is a service that runs indefinitely and executes "jobs" at certain times or on certain conditions. Jobs are tasks that can involve anything from databases updates/backups to scraping of a website(s) - the choice is yours.

Installing Quartz.NET

To get started using Quartz.NET, you must first add the Nuget package to your .NET solution:

Next, we need to head over to our Web.config file and add the following keys:

<quartz>
    <add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz"/>
    <add key="quartz.threadPool.threadCount" value="10" />
    <add key="quartz.threadPool.threadPriority" value="Normal" />
    <add key="quartz.jobStore.useProperties" value="true" />
    <add key="quartz.jobStore.clustered" value="false" />
</quartz>

Here, we are telling Quartz to store all data that's required within RAM. We can specify a flavour of DB for persistent storage. For for simple tutorial, we will just be using the in memory solution as it requires much less configuration.

Using Quartz.NET

Now we are ready to start using Quartz.NET! There are four main components to Quartz.NET:

  1. Schedulers
  2. Triggers
  3. Jobs
  4. Listeners

A diagram of how the schedulers, triggers and jobs components work together is below:

To get started using Quartz.NET, lets create our first Quartz scheduler. A scheduler is in charge of making sure Jobs run when Triggers are fired. Creating a scheduler instance is easy, first create a new instance of the Scheduler Factory that comes with Quartz and use it to create a new scheduler instance for us. Doing this will look like:

public class QuartzTutorial {
    ISchedulerFactory schedFact = new StdSchedulerFactory();

    IScheduler sched = schedFact.GetScheduler();
    sched.Start();
}

We can now create Triggers and Jobs to which we assign to the scheduler instance we just created.

To create a job, we create a new class that implements the IJob interface. The IJob interface only requires implementation of one method - Execute. Whatever code is within the Execute method will be run once the Job is triggered with a Trigger.

public class ExampleJob: IJob 
{
    public void Execute(IJobExecutionContext context)
    {
        for (var i = 0; i < 10; i++)
        {
            Console.WriteLine("Quartz Job Loop: " + i);
        }
    }
}

The ExampleJob class now needs a way to actually be executed. To do this, we create an instance of the job using the JobBuilder class (from Quartz). We use the Create method passing the job type - ExampleJob - that we just created. This will look like the following:

IJobDetail job = JobBuilder.Create<ExampleJob>()
    .WithIdentity(Guid.NewGuid().ToString)
    .Build();

A Trigger is attached to a job + scheduler and tells the scheduler when to run the job it's attached to. There are a number of options for how you can trigger a job, some examples include:

  • Running a job at a certain time of the day
  • Running a job on a particular day of the week/month/year
  • Running a job for a set number of times
  • Running a job that repeats until a specific date/time.
  • Running a job with a delay interval
    • combinations of all the above.

As you can see, there are so many ways you are able to trigger jobs. But we will just go with a simple definition here to just start the job immediately:

ITrigger trigger = TriggerBuilder.Create()
    .WithIdentity("myTrigger", "group1")
    .StartNow()
    .Build();

Putting it all together:

public class QuartzTutorial 
{
    ISchedulerFactory schedFact = new StdSchedulerFactory();
    
    IScheduler sched = schedFact.GetScheduler();
    sched.Start();

    IJobDetail job = JobBuilder.Create<ExampleJob>()
        .WithIdentity(Guid.NewGuid().ToString)
        .Build();

    ITrigger trigger = TriggerBuilder.Create()
        .WithIdentity(Guid.NewGuid().ToString)
        .StartNow()
        .Build();

    sched.Schedule(job, trigger);
}

Pretty easy, right?

Now let's cover Listeners. Listeners, as one might expect, listen for certain events that happen within Quartz. We can create listeners for Schedulers, Triggers and Jobs. We will only be covering Job Listeners.

To create a Job Listener, we have to implement the IJobListener interface:

public interface IJobListener
{
    string Name { get; }
    void JobExecutionVetoed(IJobExecutionContext context);
    void JobToBeExecuted(IJobExecutionContext context);
    void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException);
}
  • Name returns the name of the listener.
  • JobExecutionVetoed executes when a job was assigned to a scheduler and trigger, but was then cancelled.
  • JobToBeExecuted runs when a job has been triggered.
  • JobWasExecuted runs when a job has finished running.

An example of a job listener is as follows:

public class ExampleJobListener: IJobListener
{
    public void JobToBeExecuted(IJobExecutionContext context)
    {
        Console.WriteLine("Job was Executed");
    }

    public void JobExecutionVetoed(IJobExecutionContext context)
    {
        Console.WriteLine("Job Was Vetoed");
    }

    public void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
    {
        Console.WriteLine("Job was Executed"):
    }

    public string Name { get { return "JobListenerName"; } 
}

You must also be sure to attach the newly created Job Listener to our Scheduler:

public class QuartzTutorial 
{  
    IScheduler scheduler = schedFact.GetScheduler();

    scheduler.ListenerManager.AddJobListener(new ExampleJobListener());
    scheduler.Start();
}

Conclusion

Thanks for reading my first technical blog post.

That's all the basics of Quartz.NET covered. Read my next two blog posts explaining how to:

  • Implement job exception retrying (when the code block within a job fails, we want the job to keep re-trying).
  • Durable job chaining using Quartz (when we need certain jobs to wait for another job to finish before running).