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