Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

Quartz.NET Usage

Component: NServiceBus
NuGet Package: NServiceBus (9.x)

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

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);

LogProvider.SetCurrentLogProvider(new QuartzConsoleLogProvider());

var schedulerFactory = new StdSchedulerFactory();

var scheduler = await schedulerFactory.GetScheduler();

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

await scheduler.Start();

Job definition

A Quartz IJob that sends a message to Receiver.

public class SendMessageJob :
    IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        try
        {
            var endpointInstance = context.EndpointInstance();
            var message = new MyMessage();
            await endpointInstance.Send("Samples.QuartzScheduler.Receiver", message);
        }
        catch (Exception exception)
        {
            Console.WriteLine($"Execution Failed: {exception.Message}");
            // 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);

Cleanup

The Quartz scheduler should be shut down when the endpoint is stopped.

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

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 block 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. If an endpoint is scaled out, then the configured jobs will be executed in each of the 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
    Message store for delayed messages sent with NServiceBus.