Quartz.NET 任务调度
大约 11 分钟约 3262 字
Quartz.NET 任务调度
简介
Quartz.NET 是 .NET 平台功能最强大的任务调度框架,源自 Java 的 Quartz 项目。它支持复杂的 Cron 调度表达式、任务持久化、集群部署、错过执行处理等企业级特性。适用于需要精确调度控制的复杂业务场景。
特点
安装与配置
NuGet 包
dotnet add package Quartz
dotnet add package Quartz.AspNetCore # ASP.NET Core 集成
dotnet add package Quartz.Serialization.Json
dotnet add package Quartz.Extensions.DependencyInjection基本配置
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 方式1:使用 Quartz.AspNetCore 扩展
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
q.UseSimpleTypeLoader();
q.UseInMemoryStore(); // 使用内存存储
// q.UseSqlServer(@"Server=...;Database=QuartzDb;"); // 数据库存储
// 注册任务
var jobKey = new JobKey("DailyReportJob");
q.AddJob<DailyReportJob>(opts => opts.WithIdentity(jobKey));
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("DailyReportJob-trigger")
.WithCronSchedule("0 2 * * *") // 每天凌晨2点
);
});
builder.Services.AddQuartzHostedService(options =>
{
options.WaitForJobsToComplete = true; // 优雅关闭
});
var app = builder.Build();
app.Run();核心概念
| 概念 | 说明 |
|---|---|
| IScheduler | 调度器 — 任务调度的核心容器 |
| IJob | 任务 — 具体要执行的工作 |
| IJobDetail | 任务详情 — 任务的描述信息 |
| ITrigger | 触发器 — 定义何时执行任务 |
| JobBuilder | 任务构建器 |
| TriggerBuilder | 触发器构建器 |
Job(任务)
/// <summary>
/// 实现 IJob 接口定义任务
/// </summary>
public class DataSyncJob : IJob
{
private readonly IDataSyncService _syncService;
private readonly ILogger<DataSyncJob> _logger;
// 支持依赖注入
public DataSyncJob(IDataSyncService syncService, ILogger<DataSyncJob> logger)
{
_syncService = syncService;
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("数据同步任务开始执行");
try
{
// 从 JobDataMap 获取参数
var source = context.JobDetail.JobDataMap.GetString("Source");
var batchSize = context.JobDetail.JobDataMap.GetInt("BatchSize");
var result = await _syncService.SyncAsync(source, batchSize);
// 将结果存入 JobDataMap
context.JobDetail.JobDataMap.Put("LastSyncCount", result.SyncedCount);
_logger.LogInformation("数据同步完成,共同步 {Count} 条", result.SyncedCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "数据同步失败");
// 抛出 JobExecutionException 可以控制重试
throw new JobExecutionException(ex, refireImmediately: false);
}
}
}Trigger(触发器)
/// <summary>
/// 三种触发器类型
/// </summary>
// 1. SimpleTrigger — 简单间隔执行
var simpleTrigger = TriggerBuilder.Create()
.WithIdentity("simple-trigger")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(30)
.RepeatForever()
)
.Build();
// 执行5次,每次间隔1分钟
var repeatTrigger = TriggerBuilder.Create()
.WithIdentity("repeat-trigger")
.StartAt(DateTimeOffset.Now.AddMinutes(5))
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(1)
.WithRepeatCount(4)
)
.Build();
// 2. CronTrigger — Cron 表达式(最常用)
var cronTrigger = TriggerBuilder.Create()
.WithIdentity("cron-trigger")
.WithCronSchedule("0 0/5 * * * ?", x => x
.WithMisfireHandlingInstructionFireAndProceed()
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"))
)
.Build();
// 3. DailyTimeIntervalTrigger — 每日时间段内执行
var dailyTrigger = TriggerBuilder.Create()
.WithIdentity("daily-trigger")
.WithDailyTimeIntervalSchedule(x => x
.OnMondayThroughFriday()
.StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(9, 0))
.EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(17, 0))
.WithIntervalInMinutes(30)
)
.Build();Cron 表达式(Quartz 7字段版)
字段顺序:秒 分 时 日 月 周 年
示例:
"0 0 2 * * ?" — 每天凌晨2点
"0 */5 * * * ?" — 每5分钟
"0 0 9 * * MON-FRI" — 工作日早上9点
"0 0 0 1 * ?" — 每月1号零点
"0 0 10,14,16 * * ?" — 每天10点、14点、16点
"0 0/30 9-17 * * ?" — 工作时间每30分钟| 字段 | 允许值 | 特殊字符 |
|---|---|---|
| 秒 | 0-59 | , - * / |
| 分 | 0-59 | , - * / |
| 时 | 0-23 | , - * / |
| 日 | 1-31 | , - * ? / L W |
| 月 | 1-12 | , - * / |
| 周 | 1-7 | , - * ? / L # |
| 年 | 可选 | , - * / |
注册任务的方式
方式1:启动时注册
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
// 带参数的任务
q.AddJob<DataSyncJob>(opts => opts
.WithIdentity("data-sync-job")
.UsingJobData("Source", "ERP")
.UsingJobData("BatchSize", 1000)
);
q.AddTrigger(opts => opts
.ForJob("data-sync-job")
.WithIdentity("data-sync-trigger")
.WithCronSchedule("0 */10 * * * ?")
);
});方式2:运行时动态创建
/// <summary>
/// 动态创建和管理任务
/// </summary>
public class JobManagementService
{
private readonly ISchedulerFactory _schedulerFactory;
private IScheduler _scheduler;
public JobManagementService(ISchedulerFactory schedulerFactory)
{
_schedulerFactory = schedulerFactory;
}
public async Task StartAsync()
{
_scheduler = await _schedulerFactory.GetScheduler();
await _scheduler.Start();
}
// 创建一次性任务
public async Task ScheduleOneTimeJobAsync<TJob>(DateTime executeTime, JobDataMap dataMap = null)
where TJob : IJob
{
var job = JobBuilder.Create<TJob>()
.WithIdentity($"{typeof(TJob).Name}-{Guid.NewGuid()}")
.SetJobData(dataMap ?? new JobDataMap())
.Build();
var trigger = TriggerBuilder.Create()
.StartAt(executeTime)
.Build();
await _scheduler.ScheduleJob(job, trigger);
}
// 创建定时任务
public async Task ScheduleRecurringJobAsync<TJob>(string jobName, string cronExpression)
where TJob : IJob
{
var jobKey = new JobKey(jobName);
// 如果已存在则删除
if (await _scheduler.CheckExists(jobKey))
{
await _scheduler.DeleteJob(jobKey);
}
var job = JobBuilder.Create<TJob>()
.WithIdentity(jobKey)
.StoreDurably()
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"{jobName}-trigger")
.WithCronSchedule(cronExpression)
.Build();
await _scheduler.ScheduleJob(job, trigger);
}
// 暂停任务
public async Task PauseJobAsync(string jobName)
{
await _scheduler.PauseJob(new JobKey(jobName));
}
// 恢复任务
public async Task ResumeJobAsync(string jobName)
{
await _scheduler.ResumeJob(new JobKey(jobName));
}
// 删除任务
public async Task DeleteJobAsync(string jobName)
{
await _scheduler.DeleteJob(new JobKey(jobName));
}
// 获取所有任务
public async Task<List<ScheduledJobInfo>> GetAllJobsAsync()
{
var groupNames = await _scheduler.GetJobGroupNames();
var result = new List<ScheduledJobInfo>();
foreach (var group in groupNames)
{
var jobKeys = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(group));
foreach (var key in jobKeys)
{
var triggers = await _scheduler.GetTriggersOfJob(key);
var detail = await _scheduler.GetJobDetail(key);
result.Add(new ScheduledJobInfo
{
JobName = key.Name,
JobGroup = key.Group,
NextFireTime = triggers.FirstOrDefault()?.GetNextFireTimeUtc()?.LocalDateTime,
PreviousFireTime = triggers.FirstOrDefault()?.GetPreviousFireTimeUtc()?.LocalDateTime,
TriggerState = triggers.Any()
? await _scheduler.GetTriggerState(triggers.First().Key)
: TriggerState.None
});
}
}
return result;
}
}监听器(Listener)
JobListener
/// <summary>
/// 全局任务监听器 — 统一日志记录
/// </summary>
public class GlobalJobListener : IJobListener
{
private readonly ILogger<GlobalJobListener> _logger;
public GlobalJobListener(ILogger<GlobalJobListener> logger)
{
_logger = logger;
}
public string Name => "GlobalJobListener";
public Task JobToBeExecuted(IJobExecutionContext context,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("任务开始:{Job},触发器:{Trigger}",
context.JobDetail.Key, context.Trigger.Key);
return Task.CompletedTask;
}
public Task JobExecutionVetoed(IJobExecutionContext context,
CancellationToken cancellationToken = default)
{
_logger.LogWarning("任务被否决:{Job}", context.JobDetail.Key);
return Task.CompletedTask;
}
public Task JobWasExecuted(IJobExecutionContext context,
JobExecutionException jobException,
CancellationToken cancellationToken = default)
{
if (jobException != null)
{
_logger.LogError(jobException, "任务执行异常:{Job}", context.JobDetail.Key);
}
else
{
_logger.LogInformation("任务完成:{Job},耗时:{Elapsed}",
context.JobDetail.Key, context.JobRunTime);
}
return Task.CompletedTask;
}
}
// 注册监听器
builder.Services.AddQuartz(q =>
{
q.AddJobListener<GlobalJobListener>();
});持久化与集群
/// <summary>
/// 数据库持久化 + 集群配置
/// </summary>
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
// ADO.NET 存储(支持 SQL Server/MySQL/PostgreSQL/Oracle)
q.UsePersistentStore(store =>
{
store.UsePropertiesAsJson(); // 将 JobDataMap 序列化为 JSON
store.UseClustering(); // 启用集群
store.UseSqlServer(builder.Configuration.GetConnectionString("Quartz"));
});
});
// 集群说明:
// 1. 所有节点连接同一个数据库
// 2. 任务只会被一个节点执行(通过数据库锁保证)
// 3. 节点故障时其他节点自动接管
// 4. 需要先创建数据库表(Quartz 提供了 SQL 脚本)Quartz vs Hangfire 对比
| 对比维度 | Quartz.NET | Hangfire |
|---|---|---|
| 学习曲线 | 较陡 | 简单 |
| Dashboard | 无内置 | 内置丰富 |
| Cron 支持 | 7字段(含秒) | 5字段(标准) |
| 持久化 | ADO.NET(多种DB) | SQL Server/Redis |
| 集群 | 原生支持 | Pro 版支持 |
| 依赖注入 | 需要配置 | 原生支持 |
| 适用场景 | 复杂调度、集群 | 简单后台任务 |
优点
缺点
总结
Quartz.NET 适合需要精确调度控制、集群部署和复杂触发规则的场景。如果只需要简单的后台任务处理,Hangfire 更轻量;如果需要企业级调度能力,Quartz 是更好的选择。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《Quartz.NET 任务调度》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《Quartz.NET 任务调度》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Quartz.NET 任务调度》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Quartz.NET 任务调度》最大的收益和代价分别是什么?
Misfire 机制详解
Misfire(错过执行)是任务调度中的重要概念。当任务因系统繁忙或停机错过了预定执行时间,Quartz 需要决定如何处理。
Misfire 场景:
1. 应用停机/重启期间错过了多次执行
2. 所有线程池线程都在忙碌,无法及时启动新任务
3. Cron 表达式设置过密(如每秒一次),系统处理不过来
Misfire 处理策略(以 CronTrigger 为例):
1. WithMisfireHandlingInstructionFireAndProceed(推荐)
- 立即执行一次错过的任务
- 然后按正常计划继续
- 适合:重要的业务任务(如每日同步)
- 不会导致任务堆积
2. WithMisfireHandlingInstructionIgnoreMisfires
- 立即执行所有错过的任务
- 危险!可能导致大量任务同时执行
- 适合:可以并行执行且资源充足的场景
3. WithMisfireHandlingInstructionDoNothing
- 忽略所有错过的任务
- 等到下一个计划时间执行
- 适合:非关键的周期性任务(如日志清理)// Misfire 配置示例
var trigger = TriggerBuilder.Create()
.WithIdentity("important-daily-sync")
.WithCronSchedule("0 0 2 * * ?", x => x
.WithMisfireHandlingInstructionFireAndProceed() // 立即补执行一次
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"))
)
.Build();
// 对于不同的任务选择不同的 Misfire 策略
public static class MisfireStrategies
{
// 关键业务任务:补执行
public static TriggerBuilder UseCriticalStrategy(this TriggerBuilder builder)
=> builder.WithCronSchedule("0 */10 * * * ?", x =>
x.WithMisfireHandlingInstructionFireAndProceed());
// 可丢弃任务:忽略
public static TriggerBuilder UseDisposableStrategy(this TriggerBuilder builder)
=> builder.WithCronSchedule("0 */5 * * * ?", x =>
x.WithMisfireHandlingInstructionDoNothing());
}Quartz.NET 日历排除
/// <summary>
/// 使用日历排除特定日期(如节假日、周末)
/// </summary>
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
// 排除节假日
var holidayCalendar = new HolidayCalendar();
holidayCalendar.AddExcludedDate(new DateTime(2025, 1, 1)); // 元旦
holidayCalendar.AddExcludedDate(new DateTime(2025, 2, 10)); // 春节
holidayCalendar.AddExcludedDate(new DateTime(2025, 5, 1)); // 劳动节
holidayCalendar.AddExcludedDate(new DateTime(2025, 10, 1)); // 国庆节
q.AddCalendar("holidays", holidayCalendar, replace: true, updateTriggers: true);
// 排除周末
var weeklyCalendar = new WeeklyCalendar();
weeklyCalendar.SetDayExcluded(DayOfWeek.Saturday, true);
weeklyCalendar.SetDayExcluded(DayOfWeek.Sunday, true);
q.AddCalendar("weekdays", weeklyCalendar, replace: true, updateTriggers: true);
// 组合:工作日 + 非节假日
var cronTrigger = TriggerBuilder.Create()
.WithIdentity("workday-report-trigger")
.WithCronSchedule("0 0 9 * * ?") // 每天 9 点
.ModifiedByCalendar("weekdays") // 排除周末
.ModifiedByCalendar("holidays") // 排除节假日
.Build();
q.AddJob<DailyReportJob>(opts => opts.WithIdentity("workday-report"));
q.AddTrigger(cronTrigger);
});任务并发控制
/// <summary>
/// 控制任务的并发行为
/// </summary>
// 1. 禁止同一 Job 并发执行(最常用)
[DisallowConcurrentExecution]
public class DataSyncJob : IJob
{
// 如果上一次执行还没完成,新的触发不会启动新实例
// 适用于:不能并行执行的数据同步任务
public async Task Execute(IJobExecutionContext context)
{
// 检查是否超时
if (context.FireTimeUtc.AddMinutes(30) < DateTimeOffset.UtcNow)
{
context.Result = "执行超时,跳过本次";
return;
}
await DoSyncWork();
}
}
// 2. 控制线程池大小
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
// 设置线程池大小(默认 10)
q.UseDefaultThreadPool(tp =>
{
tp.MaxConcurrency = 5; // 最多 5 个任务同时执行
});
});
// 3. 任务优先级控制
// 当线程池繁忙时,优先级高的任务先执行
q.AddTrigger(opts => opts
.ForJob("high-priority-job")
.WithIdentity("high-priority-trigger")
.WithCronSchedule("0 */5 * * * ?")
.WithPriority(10) // 默认优先级为 5,值越大越优先
);
q.AddTrigger(opts => opts
.ForJob("low-priority-job")
.WithIdentity("low-priority-trigger")
.WithCronSchedule("0 */5 * * * ?")
.WithPriority(1)
);实战:定时任务管理 API
/// <summary>
/// 提供运行时管理定时任务的 REST API
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class SchedulerController : ControllerBase
{
private readonly ISchedulerFactory _schedulerFactory;
public SchedulerController(ISchedulerFactory schedulerFactory)
{
_schedulerFactory = schedulerFactory;
}
[HttpGet("jobs")]
public async Task<IActionResult> GetAllJobs()
{
var scheduler = await _schedulerFactory.GetScheduler();
var groupNames = await scheduler.GetJobGroupNames();
var jobs = new List<object>();
foreach (var group in groupNames)
{
var jobKeys = await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(group));
foreach (var key in jobKeys)
{
var detail = await scheduler.GetJobDetail(key);
var triggers = await scheduler.GetTriggersOfJob(key);
jobs.Add(new
{
JobName = key.Name,
JobGroup = key.Group,
Description = detail?.Description,
TriggerInfo = triggers.Select(t => new
{
t.Key.Name,
t.Key.Group,
State = scheduler.GetTriggerState(t.Key).Result,
NextFireTime = t.GetNextFireTimeUtc()?.LocalDateTime,
PreviousFireTime = t.GetPreviousFireTimeUtc()?.LocalDateTime
})
});
}
}
return Ok(jobs);
}
[HttpPost("jobs/{jobName}/pause")]
public async Task<IActionResult> PauseJob(string jobName)
{
var scheduler = await _schedulerFactory.GetScheduler();
await scheduler.PauseJob(new JobKey(jobName));
return Ok(new { message = $"任务 {jobName} 已暂停" });
}
[HttpPost("jobs/{jobName}/resume")]
public async Task<IActionResult> ResumeJob(string jobName)
{
var scheduler = await _schedulerFactory.GetScheduler();
await scheduler.ResumeJob(new JobKey(jobName));
return Ok(new { message = $"任务 {jobName} 已恢复" });
}
[HttpPost("jobs/{jobName}/trigger")]
public async Task<IActionResult> TriggerJob(string jobName)
{
var scheduler = await _schedulerFactory.GetScheduler();
await scheduler.TriggerJob(new JobKey(jobName));
return Ok(new { message = $"任务 {jobName} 已手动触发" });
}
}