Quartz.NET для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Quartz.NET для C# (.NET): Полное руководство по планированию задач

Quartz.NET — это мощная, гибкая и широко используемая библиотека планирования задач с открытым исходным кодом для приложений .NET. Она позволяет разработчикам создавать, управлять и выполнять фоновые задания по расписанию. Если вам нужно запускать код в определенное время, с заданной периодичностью или в ответ на какое-либо событие (например, кронджобы в Linux), Quartz.NET — это правильный выбор. Библиотека является портом популярного Java-фреймворка Quartz Scheduler.

Зачем она нужна? В современной разработке часто возникают задачи, которые не должны выполняться синхронно по запросу пользователя. Это может быть отправка email-рассылок, очистка временных файлов, синхронизация данных с внешними API, генерация отчетов, обновление кэша и многое другое. Quartz.NET предоставляет надежный и отказоустойчивый механизм для выполнения таких задач. Он поддерживает сохранение состояния заданий в базе данных (JobStore), что позволяет переживать перезапуски приложения, а также поддерживает кластеризацию для распределения нагрузки.

В отличие от простого использования System.Threading.Timer или BackgroundService, Quartz.NET предлагает богатый API для управления жизненным циклом заданий, сложные триггеры (ежедневно, еженедельно, по CRON-выражениям), параллельное выполнение, прослушивание событий и многое другое. Это делает его стандартом де-факто для планирования в экосистеме .NET.

Установка

Quartz.NET распространяется через NuGet. Для установки используйте одну из следующих команд в консоли диспетчера пакетов:

# Установка основного пакета
dotnet add package Quartz

# Если вам нужна поддержка XML-конфигурации
dotnet add package Quartz.Plugins

# Для использования с Dependency Injection (рекомендуется для ASP.NET Core)
dotnet add package Quartz.Extensions.Hosting

Для классического .NET Framework (например, .NET 4.8) используйте консоль NuGet:

Install-Package Quartz

Быстрый старт: Минимальный рабочий пример

Давайте создадим простое консольное приложение, которое будет выводить "Hello, Quartz!" каждые 5 секунд.

using System;
using System.Threading.Tasks;
using Quartz;
using Quartz.Impl;

namespace QuartzQuickStart
{
    // 1. Создаем Job (задание)
    public class HelloJob : IJob
    {
        public async Task Execute(IJobExecutionContext context)
        {
            await Console.Out.WriteLineAsync($"Hello, Quartz! Время: {DateTime.Now:HH:mm:ss}");
        }
    }

    class Program
    {
        static async Task Main(string[] args)
        {
            // 2. Создаем фабрику планировщика
            StdSchedulerFactory factory = new StdSchedulerFactory();

            // 3. Получаем планировщик
            IScheduler scheduler = await factory.GetScheduler();

            // 4. Запускаем планировщик
            await scheduler.Start();

            // 5. Определяем задание (Job)
            IJobDetail job = JobBuilder.Create()
                .WithIdentity("myJob", "group1")
                .Build();

            // 6. Определяем триггер (расписание)
            ITrigger trigger = TriggerBuilder.Create()
                .WithIdentity("myTrigger", "group1")
                .StartNow()
                .WithSimpleSchedule(x => x
                    .WithIntervalInSeconds(5)
                    .RepeatForever())
                .Build();

            // 7. Регистрируем задание и триггер в планировщике
            await scheduler.ScheduleJob(job, trigger);

            // 8. Ждем, чтобы консоль не закрылась
            await Task.Delay(TimeSpan.FromSeconds(30));

            // 9. Останавливаем планировщик
            await scheduler.Shutdown();
        }
    }
}

Основные классы, методы и интерфейсы

Ниже представлены ключевые компоненты Quartz.NET с подробным описанием и примерами.

1. IJob и IJob.Execute(IJobExecutionContext context)

Сигнатура: Task Execute(IJobExecutionContext context)

Что делает: Это основной интерфейс, который должен реализовать ваш класс для выполнения работы. Метод Execute вызывается планировщиком, когда срабатывает триггер. IJobExecutionContext предоставляет контекст выполнения (данные задания, планировщик, время срабатывания и т.д.).

Пример:

public class SendEmailJob : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        // Получаем данные из JobDataMap
        var email = context.MergedJobDataMap.GetString("email");
        Console.WriteLine($"Отправка письма на {email}...");
        await Task.Delay(100); // Имитация отправки
        Console.WriteLine("Письмо отправлено.");
    }
}

2. JobBuilder

Сигнатура (основные методы): JobBuilder.Create(), .WithIdentity(string name, string group), .UsingJobData(string key, string value), .Build()

Что делает: Статический класс-строитель для создания экземпляров IJobDetail. Позволяет задать тип задания, его уникальный идентификатор (имя и группа), а также передать произвольные данные (JobDataMap).

Пример:

IJobDetail job = JobBuilder.Create()
    .WithIdentity("emailJob", "notifications")
    .UsingJobData("email", "user@example.com")
    .UsingJobData("template", "welcome")
    .Build();

3. TriggerBuilder

Сигнатура (основные методы): TriggerBuilder.Create(), .WithIdentity(string name, string group), .StartNow(), .WithSimpleSchedule(Action<SimpleScheduleBuilder> action), .WithCronSchedule(string cronExpression), .Build()

Что делает: Статический класс-строитель для создания триггеров. Определяет, когда и как часто должно запускаться задание.

Пример (SimpleSchedule):

ITrigger trigger = TriggerBuilder.Create()
    .WithIdentity("trigger1", "group1")
    .StartNow()
    .WithSimpleSchedule(x => x
        .WithIntervalInMinutes(10)
        .RepeatForever())
    .Build();

Пример (CronSchedule):

ITrigger cronTrigger = TriggerBuilder.Create()
    .WithIdentity("cronTrigger", "group1")
    .WithCronSchedule("0 0 8 ? * MON-FRI") // Каждый будний день в 8:00 утра
    .Build();

4. IScheduler

Сигнатура (основные методы): Task Start(), Task Shutdown(bool waitForJobsToComplete = true), Task ScheduleJob(IJobDetail job, ITrigger trigger), Task DeleteJob(JobKey jobKey), Task PauseJob(JobKey jobKey), Task ResumeJob(JobKey jobKey)

Что делает: Главный интерфейс планировщика. Управляет жизненным циклом заданий и триггеров. Через него вы запускаете/останавливаете планировщик, регистрируете и удаляете задания.

Пример:

IScheduler scheduler = await factory.GetScheduler();
await scheduler.Start();

// ... регистрация заданий ...

await scheduler.PauseJob(new JobKey("myJob", "group1"));
await scheduler.ResumeJob(new JobKey("myJob", "group1"));
await scheduler.DeleteJob(new JobKey("myJob", "group1"));

await scheduler.Shutdown();

5. StdSchedulerFactory

Сигнатура: Task<IScheduler> GetScheduler()

Что делает: Фабрика по умолчанию для создания экземпляра IScheduler. Она читает конфигурацию из файла quartz.config (если он есть) или использует настройки по умолчанию (RAMJobStore — хранение в памяти).

Пример:

// Простейший способ
StdSchedulerFactory factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler();

// С передачей свойств
var properties = new NameValueCollection
{
    ["quartz.scheduler.instanceName"] = "MyScheduler",
    ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"
};
StdSchedulerFactory factoryWithProps = new StdSchedulerFactory(properties);

6. IJobDetail

Сигнатура (свойства): JobKey Key { get; }, JobDataMap JobDataMap { get; }, bool IsDurable { get; }

Что делает: Интерфейс, описывающий детали задания. Содержит ключ (имя + группа), данные, передаваемые в задание, и флаг "долговечности" (будет ли задание сохранено, если у него нет активных триггеров).

Пример:

IJobDetail jobDetail = JobBuilder.Create().Build();
JobKey key = jobDetail.Key; // key.Name, key.Group
JobDataMap dataMap = jobDetail.JobDataMap;

7. JobDataMap

Сигнатура (основные методы): Put(string key, object value), GetString(string key), GetInt(string key), GetBoolean(string key)

Что делает: Словарь для хранения произвольных данных, которые можно передать в задание при его создании. Это удобно для параметризации заданий без создания новых классов.

Пример:

// Передача данных
IJobDetail job = JobBuilder.Create()
    .UsingJobData("userId", 123)
    .UsingJobData("isAdmin", true)
    .Build();

// Чтение данных внутри MyJob.Execute()
public class MyJob : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        int userId = context.MergedJobDataMap.GetInt("userId");
        bool isAdmin = context.MergedJobDataMap.GetBoolean("isAdmin");
        Console.WriteLine($"User {userId} is admin: {isAdmin}");
    }
}

8. CronExpression и CronScheduleBuilder

Сигнатура (статический метод): CronScheduleBuilder.CronSchedule(string cronExpression)

Что делает: Позволяет создавать сложные расписания на основе CRON-выражений. CRON-выражение состоит из 6 или 7 полей (секунды, минуты, часы, день месяца, месяц, день недели, год). Это самый гибкий способ задания расписания.

Пример:

// Каждый день в 12:00 и 18:00
ITrigger trigger = TriggerBuilder.Create()
    .WithCronSchedule("0 0 12,18 * * ?")
    .Build();

// Каждый понедельник в 9:30 утра
ITrigger trigger2 = TriggerBuilder.Create()
    .WithCronSchedule("0 30 9 ? * MON")
    .Build();

// Каждые 5 минут
ITrigger trigger3 = TriggerBuilder.Create()
    .WithCronSchedule("0 0/5 * * * ?")
    .Build();

9. IJobListener и ITriggerListener

Сигнатура (методы): Task JobToBeExecuted(IJobExecutionContext context), Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException), Task TriggerFired(ITrigger trigger, IJobExecutionContext context), Task TriggerMisfired(ITrigger trigger)

Что делает: Интерфейсы-слушатели для перехвата событий жизненного цикла заданий и триггеров. Позволяют выполнять дополнительную логику до/после выполнения задания или при ошибках.

Пример:

public class MyJobListener : IJobListener
{
    public string Name => "MyJobListener";

    public async Task JobToBeExecuted(IJobExecutionContext context)
    {
        await Console.Out.WriteLineAsync($"Задание {context.JobDetail.Key} скоро начнется.");
    }

    public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
    {
        if (jobException != null)
            await Console.Out.WriteLineAsync($"Задание {context.JobDetail.Key} завершилось с ошибкой.");
        else
            await Console.Out.WriteLineAsync($"Задание {context.JobDetail.Key} успешно выполнено.");
    }

    public Task JobExecutionVetoed(IJobExecutionContext context) => Task.CompletedTask;
}

// Регистрация слушателя
scheduler.ListenerManager.AddJobListener(new MyJobListener(), GroupMatcher.AnyGroup());

Реальный практический пример: Система уведомлений

Создадим систему, которая проверяет базу данных каждые 30 секунд на наличие новых заказов и отправляет уведомление, если заказ просрочен (например, не обработан в течение 1 минуты).

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Quartz;
using Quartz.Impl;

namespace OrderNotificationSystem
{
    // Модель заказа
    public class Order
    {
        public int Id { get; set; }
        public DateTime CreatedAt { get; set; }
        public bool IsProcessed { get; set; }
    }

    // Имитация базы данных
    public static class FakeDatabase
    {
        private static readonly List Orders = new List
        {
            new Order { Id = 1, CreatedAt = DateTime.Now.AddMinutes(-5), IsProcessed = false },
            new Order { Id = 2, CreatedAt = DateTime                    

Рекомендаций