Quartz.NET Usage

Component: NServiceBus
NuGet Package NServiceBus (7-pre)
This page targets a pre-release version and is subject to change prior to the final release.

This sample illustrates the use of Quartz.NET to send messages from within an NServiceBus endpoint.

Quartz.NET is a full-featured, open source job scheduling system that can be used from smallest apps to large scale enterprise systems.

The approach used in this sample can mitigate some of the architectural drawbacks of the NServiceBus Scheduler. The NServiceBus scheduler is built on top of the Timeout Manager which leverages the queuing system to trigger scheduled actions. Under heavy load there may be some disparity between the expected time of a scheduled action and execution time due to the delay between timeout messages being generated and processed.

Running the project

  1. Start both the Scheduler and Receiver projects.
  2. At startup Scheduler will schedule a message send to Receiver every 3 seconds.
  3. Receiver will handle the message.

Code Walk-through

This sample uses a pre-release package of Quartz version 3 to reduce the friction with the NServiceBus async messaging api.

Context Helper

A helper to inject and extract the IEndpointInstance from the Quartz scheduler context.

public static class QuartzContextExtensions
{
    public static IEndpointInstance EndpointInstance(this IJobExecutionContext context)
    {
        return (IEndpointInstance) context.Scheduler.Context["EndpointInstance"];
    }

    public static void SetEndpointInstance(this IScheduler scheduler, IEndpointInstance instance)
    {
        scheduler.Context["EndpointInstance"] = instance;
    }
}

Quartz also support Dependency Injection (DI) via the JobFactory API.

Configure and start the scheduler

The endpoint is started and the IEndpointInstance is injected into the Quartz scheduler context.

var endpointInstance = await Endpoint.Start(endpointConfiguration)
    .ConfigureAwait(false);

var schedulerFactory = new StdSchedulerFactory();

var scheduler = await schedulerFactory.GetScheduler()
    .ConfigureAwait(false);

// inject the endpointInstance into the scheduler context
scheduler.SetEndpointInstance(endpointInstance);

await scheduler.Start()
    .ConfigureAwait(false);

Job definition

A Quartz IJob that sends a message to Receiver.

public class SendMessageJob :
    IJob
{
    static ILog log = LogManager.GetLogger<SendMessageJob>();

    public async Task Execute(IJobExecutionContext context)
    {
        try
        {
            var endpointInstance = context.EndpointInstance();
            var message = new MyMessage();
            await endpointInstance.Send("Samples.QuartzScheduler.Receiver", message)
                .ConfigureAwait(false);
        }
        catch (Exception exception)
        {
            log.Fatal("Execution Failed", exception);
            // TODO: handle exception and dont throw.
            // consider implementing a circuit breaker
            throw;
        }
    }
}

Note QuartzContextExtensions is used to get access to the IEndpointInstance .

Schedule a job

// define the job and tie it to the SendMessageJob class
var job = JobBuilder.Create<SendMessageJob>()
    .WithIdentity("job1", "group1")
    .Build();

// Trigger the job to run now, and then repeat every 3 seconds
var trigger = TriggerBuilder.Create()
    .WithIdentity("trigger1", "group1")
    .StartNow()
    .WithSimpleSchedule(
        action: builder =>
        {
            builder
                .WithIntervalInSeconds(3)
                .RepeatForever();
        })
    .Build();

// Tell quartz to schedule the job using the trigger
await scheduler.ScheduleJob(job, trigger)
    .ConfigureAwait(false);

Cleanup

The Quartz scheduler should be shutdown when the endpoint is shutdown.

await scheduler.Shutdown()
    .ConfigureAwait(false);
await endpointInstance.Stop()
    .ConfigureAwait(false);

For cleanup purpose either a static variable may need to be kept or the shutdown done as part of dependency injection cleanup.

Logging

Quartz.NET uses LibLog. Since LibLog support the detection and utilization of Serilog, this sample use the NServiceBus Serilog integration

Log.Logger = new LoggerConfiguration()
    .WriteTo.ColoredConsole()
    .CreateLogger();
LogManager.Use<SerilogFactory>();

LibLog supports many other common logging libraries. Or Quartz can be configured to use a custom logger. See Adding logging in Quartz.NET.

Exception Handling

Quartz recommendations for Handling Exceptions:

Every listener method should contain a try-catch block that handles all possible exceptions. If a listener throws an exception, it may cause other listeners not to be notified and/or prevent the execution of the job, etc.

In the catch of a job consider either implementing a circuit breaker or delegating to critical-errors.

Scale Out

When using the approach in the sample, it is important to note that there is an instance of the Quartz scheduler running in every endpoint instance. So if an endpoint is scaled out the configured jobs will be executed in each of those running instances. A persistent Quartz JobStore can help manage the the Quartz scheduler shared state including jobs, triggers, calendars, etc.

Further information on Quartz

Related Articles

  • Scheduling
    Schedule a task or an action/lambda, to be executed repeatedly at a given interval.
  • Timeout Manager
    NServiceBus persistent delayed message store.

Last modified