Quartz.NET全面解析与实战指南
Quartz.NET是.NET平台的企业级定时任务框架,支持Cron表达式、任务持久化、集群部署及依赖注入。3.0版本全面异步化。核心组件包括Job、Trigger、Scheduler、JobStore和ThreadPool。提供简单间隔、Cron和每日时间区间触发器,支持内存与数据库存储,通过监听器实现任务监控。
聊到 .NET 生态里的定时任务框架,Quartz.NET 绝对是个绕不开的名字。这个源自 Ja va 世界 Quartz 的开源项目,被移植到 .NET 平台后,经过多年迭代,已经成为企业级任务调度的中坚力量。无论是每天定时发送报表、轮询某个外部接口,还是在工作日特定时段执行数据同步,它都能胜任。简单来说,它的核心能力就是一句话:在指定的时间,或者按指定的频率,把任务给你办了。
说到特性,Quartz.NET 有几个比较能打的点:用 Cron 表达式搞定灵活的日历调度,能把任务信息持久化到数据库里,即使应用重启任务也不丢;支持分布式集群部署,实现负载均衡和故障转移;还能在运行时动态管理任务,添加、暂停、恢复、删除都行。对于 ASP.NET Core 项目,它对依赖注入的深度集成也做得很到位。另外,插件和监听器机制让你可以自定义调度逻辑和监控手段。
版本提醒一句:Quartz.NET 3.0 是个里程碑,它全面拥抱了
async/await,并对 .NET Core 提供了原生支持。NuGet 包也做了拆分,像Quartz.Jobs、Quartz.Plugins这些,都成了独立的依赖项。
核心概念与架构
要上手 Quartz.NET,得先搞明白它的几个核心组件,它们构成了整个调度系统的基础。
| 组件 | 描述 |
|---|---|
| Job(作业) | 实现 IJob 接口的类,里面放着你要执行的业务逻辑。 |
| Trigger(触发器) | 定义任务什么时候干,比如间隔多久,或者用 Cron 表达式定个日历规则。 |
| Scheduler(调度器) | 整个系统的引擎,负责把 Job 和 Trigger 管起来,让它们按规矩干活。 |
| JobStore(作业存储) | 存放作业和触发器的信息,有内存和数据库两种模式可选。 |
| ThreadPool(线程池) | 给任务执行提供现场资源,确保干活时不堵车。 |
基本工作流也比较清晰:先定义一个 Job 类,实现 IJob 接口,写业务逻辑;再创建一个 Trigger,设定触发规则;然后通过 Scheduler 把它们俩绑定在一起,启动调度;最后调度器会在指定时机,通过线程池把 Job 跑起来。
3.x 核心变化:全面异步化
从 3.0 版本开始,Quartz.NET 彻底转向异步。IJob 的 Execute 方法现在返回 Task,可以直接塞进 async 代码。如果你没有任何异步逻辑,返回 Task.CompletedTask 就行。
public class MyJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Task.Delay(1);
}
}
快速安装与配置
1. 安装 NuGet 包
# 核心包 dotnet add package Quartz # ASP.NET Core 托管集成 dotnet add package Quartz.Extensions.Hosting # 如果需要持久化支持(比如 SQL Server) dotnet add package Quartz.Plugins dotnet add package Quartz.Serialization.Json
2. 定义 Job 类
using Quartz;
public class HelloJob : IJob
{
private readonly ILogger _logger;
public HelloJob(ILogger logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Hello! 当前时间: {Time}", DateTime.Now);
return Task.CompletedTask;
}
}
这里提一下 IJobExecutionContext,它会向 Job 传递上下文信息,里面有个 JobDataMap,可以在 Trigger 或 Scheduler 级别传递参数。
3. 注册 Quartz 到 DI 容器
在 Program.cs 里注册相当简洁:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddQuartz(q =>
{
var jobKey = new JobKey("HelloJob");
q.AddJob(opts => opts.WithIdentity(jobKey));
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("HelloJob-trigger")
.WithSimpleSchedule(x => x.WithIntervalInSeconds(10).RepeatForever()));
});
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
var app = builder.Build();
app.Run();
触发器(Trigger)详解
Trigger 是 Quartz.NET 里最灵活的组件,可以说,它决定了任务什么时候执行。
1. SimpleSchedule —— 简单间隔调度
适用于“每隔 N 秒/分钟/小时执行一次”这种固定间隔场景:
q.AddTrigger(opts => opts
.ForJob("HelloJob")
.WithIdentity("simple-trigger")
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(30)
.RepeatForever()));
也可以控制重复次数和起止时间,比如 x.WithRepeatCount(10) 表示总共执行 10 次后停止。
2. CronSchedule —— Cron 表达式调度
需要复杂日历规则时,Cron 表达式是利器。比如“每个工作日早上 9 点”:
q.AddTrigger(opts => opts
.ForJob("HelloJob")
.WithIdentity("cron-trigger")
.WithCronSchedule("0 0 9 ? * MON-FRI"));
3. Cron 表达式详解
Quartz.NET 用的是7 字段(年可选)的 Cron 表达式:
| 字段 | 是否必填 | 允许值 | 允许的特殊字符 |
|---|---|---|---|
| 秒 | 是 | 0-59 | , - * / |
| 分 | 是 | 0-59 | , - * / |
| 小时 | 是 | 0-23 | , - * / |
| 日期 | 是 | 1-31 | , - * ? / L W |
| 月份 | 是 | 1-12 或 JAN-DEC | , - * / |
| 星期 | 是 | 1-7 或 SUN-SAT | , - * ? / L # |
| 年 | 否 | 空或 1970-2099 | , - * / |
比如 0 0 12 ? * WED 就是“每周三中午 12:00”。
常用特殊字符:
| 字符 | 含义 | 示例 |
|---|---|---|
* | 所有值 | * * * * * ? 表示每秒 |
? | 不指定值(日期和星期互斥) | 比如日期字段为?时,不关心具体是哪一天 |
- | 范围 | MON-FRI 表示周一至周五 |
, | 列举 | MON,WED,FRI 表示周一、三、五 |
/ | 增量 | 0/15 在分钟字段表示每 15 分钟,从第 0 分钟开始 |
L | 最后 | L 在日期字段表示当月最后一天 |
# | 第 N 个 | 6#3 在星期字段表示第 3 个周五 |
常用表达式示例:
0 0/5 * * * ? # 每 5 分钟执行一次 0 0 2 * * ? # 每天凌晨 2 点执行 0 15 10 ? * MON-FRI # 周一至周五上午 10:15 执行 0 0 9 ? * 6L # 每月最后一个周五上午 9 点 0 0/30 9-17 ? * MON-FRI # 工作日 9:00-17:00 内每 30 分钟执行
4. 其他高级触发器
Quartz.NET 还提供了 DailyTimeIntervalTrigger,可以方便地定义每日时间段内的调度:
var trigger = TriggerBuilder.Create()
.WithIdentity("officeHoursTrigger")
.WithSchedule(DailyTimeIntervalScheduleBuilder.Create()
.OnMondayThroughFriday()
.StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(9, 0))
.EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(17, 0))
.WithIntervalInHours(2))
.Build();
依赖注入与 Job 工厂
Quartz.NET 天然支持 ASP.NET Core 的依赖注入系统。创建带依赖的 Job 非常简单:
public class EmailJob : IJob
{
private readonly IEmailService _emailService;
public EmailJob(IEmailService emailService)
{
_emailService = emailService;
}
public async Task Execute(IJobExecutionContext context)
{
await _emailService.SendAsync("定时邮件", "来自 Quartz.NET 的问候");
}
}
builder.Services.AddTransient();
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
q.AddJob(opts => opts.WithIdentity("EmailJob"));
});
UseMicrosoftDependencyInjectionJobFactory() 告诉 Quartz 使用微软的 DI 容器来创建 Job 实例,这样构造函数里注入的服务就能被正确解析。
任务持久化(JobStore)
Quartz.NET 提供两种存储模式:
1. RAMJobStore —— 内存存储
这是默认模式,所有数据都在内存里,速度最快。但应用一停,任务信息就丢了。适合测试环境,或者对任务丢失不敏感的轻量场景。
2. AdoJobStore —— 数据库持久化
把 Jobs 和 Triggers 存到关系型数据库里,确保应用重启后任务还在,这也是集群部署的基础。
配置步骤:
(1)先创建数据库表。官方提供了 SQL 建表脚本,在 database/dbtables 目录下,所有表默认以 QRTZ_ 为前缀。
(2)配置连接字符串和 JobStore。Quartz.NET 4.1 及以上版本支持在 appsettings.json 里做层次化 JSON 配置,更直观:
{
"Quartz": {
"Scheduler": {
"InstanceName": "My Scheduler",
"InstanceId": "AUTO"
},
"ThreadPool": {
"MaxConcurrency": 10
},
"JobStore": {
"Type": "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
"DataSource": "default",
"TablePrefix": "QRTZ_"
},
"DataSource": {
"default": {
"Provider": "SqlServer",
"ConnectionString": "Server=localhost;Database=quartznet"
}
}
}
}
(3)在代码里加载 JSON 配置:
builder.Services.AddQuartz(Configuration.GetSection("Quartz"), q =>
{
// 代码配置可与 JSON 配置并存
});
监听器(Listeners)—— AOP 式任务监控
监听器是 Quartz.NET 里实现任务监控和横切关注点的关键机制。通过实现监听器接口,可以在调度生命周期中插入自定义逻辑,比如日志记录、性能监控、告警通知等。
1. JobListener —— 监听 Job 事件
可以接收 Job 执行相关的三个事件:
public class LoggingJobListener : JobListenerSupport
{
public override string Name => "LoggingJobListener";
public override ValueTask JobToBeExecuted(IJobExecutionContext context)
{
Console.WriteLine($"Job [{context.JobDetail.Key}] 即将执行...");
return ValueTask.CompletedTask;
}
public override ValueTask JobWasExecuted(IJobExecutionContext context,
JobExecutionException? jobException)
{
if (jobException == null)
Console.WriteLine($"Job [{context.JobDetail.Key}] 执行成功");
else
Console.WriteLine($"Job [{context.JobDetail.Key}] 执行失败: {jobException.Message}");
return ValueTask.CompletedTask;
}
}
2. TriggerListener —— 监听 Trigger 事件
可以接收触发器相关的四个事件:
public class MetricsTriggerListener : TriggerListenerSupport
{
public override string Name => "MetricsTriggerListener";
public override ValueTask TriggerFired(ITrigger trigger, IJobExecutionContext context)
{
// 触发器触发时记录指标
return ValueTask.CompletedTask;
}
public override ValueTask TriggerMisfired(ITrigger trigger)
{
// 处理错过触发的情况
Console.WriteLine($"Trigger [{trigger.Key}] 错过触发!");
return ValueTask.CompletedTask;
}
}
几点关键提醒:监听器里要确保不抛出未捕获异常,否则可能导致作业卡死;监听器注册在运行时,不会存入 JobStore,所以每次应用启动都得重新注册;建议继承
JobListenerSupport或TriggerListenerSupport这些辅助基类,只重写感兴趣的方法就好。
3. 注册监听器
监听器通过 ListenerManager 注册,配合 Matcher 可以精确控制作用范围:
scheduler.ListenerManager.AddJobListener(
myJobListener,
KeyMatcher.KeyEquals(new JobKey("myJobName", "myJobGroup")));
scheduler.ListenerManager.AddJobListener(
myJobListener,
GroupMatcher.GroupEquals("myJobGroup"));
scheduler.ListenerManager.AddJobListener(
myJobListener,
GroupMatcher.AnyGroup());
高级特性详解
1. 集群部署
Quartz.NET 支持多节点集群,实现负载均衡和故障转移。所有节点共享同一个数据库(通过 AdoJobStore),通过数据库锁机制确保同一个 Job 不会被多个节点同时执行。
配置要点:
- 所有节点共享同一套数据库和表结构。
- 设置
org.quartz.jobStore.isClustered = true。 - 各节点的线程池参数保持一致。
- 确保各节点时钟同步。
适用场景:最适用于将长时间运行的计算密集型任务分布到多个节点执行,实现负载分担。
2. 多调度器(Multiple Schedulers)
从 Quartz.NET 4.x 开始,可以通过 AddQuartz(string name, ...) 创建命名调度器,实现工作负载隔离:
builder.Services.AddQuartz("FastScheduler", q =>
{
q.UseInMemoryStore();
q.UseDefaultThreadPool(tp => tp.MaxConcurrency = 5);
q.ScheduleJob(trigger => trigger
.WithSimpleSchedule(x => x.WithIntervalInSeconds(30).RepeatForever()));
});
builder.Services.AddQuartz("DurableScheduler", q =>
{
q.UsePersistentStore(s =>
{
s.UseSqlServer(sql => sql.ConnectionString = "your connection string");
});
q.ScheduleJob(trigger => trigger
.WithCronSchedule("0 0 2 * * ?"));
});
builder.Services.AddQuartzHostedService(options =>
{
options.WaitForJobsToComplete = true;
});
这种做法可以让不同的调度器使用不同的存储策略、线程池和配置,彼此完全隔离。
3. 动态作业管理
通过 IScheduler 接口可以在运行时动态管理任务:
await scheduler.ScheduleJob(jobDetail, trigger); await scheduler.PauseJob(jobKey); await scheduler.ResumeJob(jobKey); await scheduler.DeleteJob(jobKey); bool exists = await scheduler.CheckExists(jobKey);
4. 传递参数 —— JobDataMap
JobDataMap 是向 Job 传递参数的机制,可以在 Trigger 定义时或 Scheduler 级别设置键值对:
q.AddTrigger(opts => opts
.ForJob("HelloJob")
.UsingJobData("RetryCount", "3")
.UsingJobData("TimeoutSeconds", "30")
.WithCronSchedule("0 0/10 * * * ?"));
生产建议:CronTrigger 应显式设置时区,参数通过 JobDataMap 传递,任务依赖最好手动触发或使用监听器实现。
Quartz.NET vs Hangfire:如何选型?
两个框架都能实现定时任务,但定位和适用场景有明显差异:
| 维度 | Quartz.NET | Hangfire |
|---|---|---|
| 核心定位 | 企业级调度引擎 | 通用后台任务处理框架 |
| 调度能力 | 复杂 Cron、日历调度、时间段约束 | 基本 Cron 表达式 |
| 持久化 | 可选(RAM / 数据库) | 默认依赖数据库持久化 |
| 可视化面板 | 无(需第三方如 Quartzmin) | 内置 Dashboard |
| 集群支持 | 原生数据库锁机制 | 基于持久化存储的任务分发 |
| 学习曲线 | 较陡峭 | 较平缓 |
| 执行精度 | 毫秒级 | 秒级 |
| 最佳场景 | 复杂调度策略、金融系统、企业批处理 | 可靠任务执行、可观测性要求高的场景 |
选型思路:如果追求快速集成、注重任务可观测性,需要内置 Dashboard 来随时查看任务状态,Hangfire 更顺手。如果需要精确、复杂的调度策略,比如金融系统里的批处理任务,Quartz.NET 在复杂调度场景下会更灵活。
最佳实践与注意事项
1. Job 设计原则
- 幂等性:Job 逻辑要设计成可以安全重复执行,避免集群或重试场景下产生重复数据。
- 轻量化:
Execute方法应尽快完成,耗时长的操作建议拆分到子任务或消息队列里异步处理。 - 异常处理:妥善处理异常,返回适当的
JobExecutionException来控制重试行为。 - 避免线程阻塞:充分利用 async/await,别阻塞调度线程。
2. 配置建议
- 生产环境必须用
AdoJobStore,避免应用重启导致任务丢失。 - 合理设置线程池大小:
MaxConcurrency默认 10,应根据任务特点和服务器资源调整。 - 配置
WaitForJobsToComplete:优雅关闭时,等待正在执行的任务完成。
3. 调试与排查
- 利用
LoggingJobHistoryPlugin记录每次作业执行历史。 - 监听器实现自定义日志和告警。
- 第三方工具如 Quartzmin 或 Quartz.NetUI(基于 Vue 的定时任务管理系统),可以提供可视化任务管理。
4. 动态作业管理
- 作业配置建议存在数据库或配置中心,通过代码动态注册,而不是硬编码。
- 利用
IScheduler接口实现运行时的暂停/恢复/删除。 - 慎用
StartNow()方法,避免应用启动时大量任务同时执行。
资源与扩展
- 官网:https://www.quartz-scheduler.net
- GitHub:https://github.com/quartznet/quartznet
- 文档:https://www.quartz-scheduler.net/documentation
- 扩展包:
- Quartz.Extensions.Hosting:ASP.NET Core 托管服务。
- Quartz.Serialization.SystemTextJson:JSON 序列化支持。
- Quartz.Plugins:提供日志、异常处理等插件。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















