# Scheduling

Time is important, particularly in distributed applications. Sophisticated systems need to schedule things, and MassTransit has extensive scheduling support.

MassTransit supports two different methods of message scheduling:

  1. Scheduler-based, using either Quartz.NET or Hangfire, where the scheduler runs in a service and schedules messages using a queue.
  2. Transport-based, using the transports built-in message scheduling/delay capabilities. In some cases, such as RabbitMQ, this requires an additional plug-in to be installed and configured.

Recurring schedules are only supported by scheduler-based solutions (Option 1).

# Configuration

Depending upon the scheduling method used, the bus must be configured to use the appropriate scheduler.

# Using the message scheduler

To use the message scheduler (outside of a consumer), use IMessageScheduler from the container.

# Quartz.NET

To use Quartz.NET, an instance of Quartz.NET must be running and configured to use the message broker.

# Internal Quartz.NET instance

MassTransit is able to connect to an existing Quartz.NET instance running in the same executable.

namespace SchedulingInternalInstance
{
    using System;
    using MassTransit;
    using Microsoft.Extensions.DependencyInjection;
    using Quartz;
    using Quartz.Spi;

    public class Program
    {
        public static void Main()
        {
            const string schedulerQueueName = "scheduler";
            var schedulerUri = new Uri($"queue:{schedulerQueueName}");

            var services = new ServiceCollection();

            services.AddMassTransit(x =>
            {
                x.AddMessageScheduler(schedulerUri);

                x.UsingRabbitMq((context, cfg) =>
                {
                    cfg.UseInMemoryScheduler(schedulerCfg =>
                    {
                        schedulerCfg.QueueName = schedulerQueueName;
                        schedulerCfg.SchedulerFactory = context.GetRequiredService<ISchedulerFactory>();
                        schedulerCfg.JobFactory = context.GetRequiredService<IJobFactory>();
                        schedulerCfg.StartScheduler = false;
                    });

                    cfg.ConfigureEndpoints(context);
                });
            });
        }
    }
}

WARNING

The code above asumes Quartz.NET is already configured using dependency injection.

# External Quartz.NET instance

MassTransit provides a Docker Image (opens new window) with Quartz.NET ready-to-run using SQL Server. A complementary SQL Server Image (opens new window) configured to run with Quartz.NET is also available. Combined, these images make getting started with Quartz easy.

# Testing

Quartz.NET can also be configured in-memory, which is great for unit testing.

Uses MassTransit.Quartz (opens new window)

namespace SchedulingInMemory
{
    using System;
    using MassTransit;
    using Microsoft.Extensions.DependencyInjection;

    public class Program
    {
        public static void Main()
        {
            var services = new ServiceCollection();

            services.AddMassTransit(x =>
            {
                x.AddMessageScheduler(new Uri("queue:scheduler"));

                x.UsingRabbitMq((context, cfg) => 
                {
                    cfg.UseInMemoryScheduler("scheduler");

                    cfg.ConfigureEndpoints(context);
                });
            });
        }
    }
}

The UseInMemoryScheduler method initializes Quartz.NET for standalone in-memory operation, and configures a receive endpoint named scheduler. The AddMessageScheduler adds IMessageScheduler to the container, which will use the same scheduler endpoint.

WARNING

Using the in-memory scheduler uses non-durable storage. If the process terminates, any scheduled messages will be lost, immediately, never to be found again. For any production system, using a standalone service is recommended with persistent storage.

# Transport-based

To configure transport-based message scheduling, refer to the transport-specific section for details.