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
- Start both the Scheduler and Receiver projects.
- At startup, Scheduler will schedule a message send to Receiver every 3 seconds.
- 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.