说实话,第一次接触 Quartz.NET 时我有点懵------一堆接口、调度器、触发器,还有那让人头疼的 Cron 表达式。但熬过那个坎之后,它成了我最信任的任务调度工具。
它适合什么场景?
- 企业级复杂调度:比如需要每年、每月、特定星期几执行的任务
- 需要持久化和集群:任务跑一半服务器挂了?Quartz 可以恢复继续跑
- 秒级精准调度:能精确到秒,且支持事务
- 分布式环境:多台机器协同干活,任务不重复
需要留意的几点
- 学习曲线确实陡------比 Hangfire 和 FluentScheduler 复杂不少
- 如果想可靠,得配持久化存储(数据库)
- 集群配置挺折腾,得踩几个坑才能跑顺
- 简单场景别用它------杀鸡用牛刀,自己找麻烦
快速上手:三步跑起来
1. 安装 NuGet
bash
Install-Package Quartz
2. 写一个任务类
实现 IJob 接口,把要干的活塞进 Execute 方法。
csharp
using Quartz;
using System;
using System.Threading.Tasks;
namespace QuartzExample
{
[DisallowConcurrentExecution] // 防止同一任务并发跑两次
public class MailJobTest : IJob
{
public async Task Execute(IJobExecutionContext context)
{
try
{
Console.WriteLine("开始发邮件......");
// 实际业务逻辑
}
catch (Exception ex)
{
Console.WriteLine($"翻车了: {ex.Message}");
}
}
}
}
3. 启动调度器并安排任务
csharp
using Quartz;
using Quartz.Impl;
using System.Threading.Tasks;
namespace QuartzExample
{
public class QuartzScheduler
{
private IScheduler _scheduler;
public async Task StartScheduler()
{
var factory = new StdSchedulerFactory();
_scheduler = await factory.GetScheduler();
await _scheduler.Start();
// 定义活儿
var job = JobBuilder.Create<MailJobTest>()
.WithIdentity("MailJob", "MailGroup")
.Build();
// 定义啥时候干(每5秒一次)
var trigger = TriggerBuilder.Create()
.WithIdentity("MailTrigger", "MailTriggerGroup")
.WithCronSchedule("0/5 * * * * ?")
.Build();
// 绑定干活的人和干活的时间
await _scheduler.ScheduleJob(job, trigger);
}
public async Task StopScheduler()
{
if (_scheduler != null)
await _scheduler.Shutdown();
}
}
}
4. 调用入口
csharp
using System;
using System.Threading.Tasks;
namespace QuartzExample
{
class Program
{
static async Task Main(string[] args)
{
var scheduler = new QuartzScheduler();
await scheduler.StartScheduler();
Console.WriteLine("调度器跑起来了,按任意键停掉它...");
Console.ReadKey();
await scheduler.StopScheduler();
}
}
}
高级技巧:处理耗时任务
如果任务执行时间可能超过调度间隔,一定要加上 [DisallowConcurrentExecution],否则上一次没跑完下一次又开始了,数据可能乱套。
csharp
[DisallowConcurrentExecution]
public class LongRunningJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Task.Delay(5000); // 模拟慢操作
Console.WriteLine("耗时任务跑完");
}
}
封装一套好用的工具类
上面那个 QuartzScheduler 类只能管一个任务。实际项目中往往要管理多个任务,而且经常和 WinForms 或 WPF 界面联动。下面这个封装版本更贴近实战:
csharp
using Quartz;
using Quartz.Impl;
using System.Threading.Tasks;
namespace UploadLogiData.Quartzs
{
public class QuartzScheduler
{
private IScheduler _scheduler;
// 假设有个主窗体 Form1
public static Form1 Form { get; set; }
public async Task ExLogiQuartz()
{
Form?.DisplayListboxMsg("任务调度启动!");
var factory = new StdSchedulerFactory();
_scheduler = await factory.GetScheduler();
await _scheduler.Start();
// 任务1:每5秒检查一次下载任务
var job1 = JobBuilder.Create<LogiDownloadJob>()
.WithIdentity("LogiJob", "LogiGroup")
.Build();
var trigger1 = TriggerBuilder.Create()
.WithIdentity("LogiTrigger", "LogiTriggerGroup")
.WithCronSchedule("0/5 * * * * ?")
.Build();
await _scheduler.ScheduleJob(job1, trigger1);
// 任务2:每天下午3:10发邮件
var job2 = JobBuilder.Create<MailJobTest>()
.WithIdentity("MailJob", "MailGroup")
.Build();
var trigger2 = TriggerBuilder.Create()
.WithIdentity("MailTrigger", "MailTriggerGroup")
.WithCronSchedule("0 10 15 * * ?")
.Build();
await _scheduler.ScheduleJob(job2, trigger2);
}
public IScheduler Scheduler => _scheduler;
public async Task ShutdownScheduler()
{
if (_scheduler != null)
await _scheduler.Shutdown();
}
}
}
调用方式:
csharp
using System;
using System.Threading.Tasks;
namespace UploadLogiData
{
class Program
{
public static async Task Main(string[] args)
{
// 关联主窗体(如果有)
QuartzScheduler.Form = new Form1();
var scheduler = new QuartzScheduler();
await scheduler.ExLogiQuartz();
Console.WriteLine("调度器已启动,按任意键关闭...");
Console.ReadKey();
await scheduler.ShutdownScheduler();
}
}
}
小提醒 :如果你的项目没有 WinForms 依赖,可以直接去掉
Form静态属性,用日志输出代替。
再说说 Cron 表达式
Quartz 的 Cron 表达式比 Linux 的多了秒字段,一共 7 个字段(年可选)。刚接触时容易写错,建议用在线工具验证。
格式 :秒 分 时 日 月 星期 [年]
| 字段 | 允许值 | 特殊字符 |
|---|---|---|
| 秒 | 0--59 | , - * / |
| 分 | 0--59 | , - * / |
| 小时 | 0--23 | , - * / |
| 日 | 1--31 | , - * ? / L W C |
| 月 | 1--12 / JAN--DEC | , - * / |
| 星期 | 1--7 / SUN--SAT | , - * ? / L C # |
| 年(可选) | 空 / 1970--2099 | , - * / |
常用例子(直接拿来用):
| 表达式 | 含义 |
|---|---|
0 0 12 * * ? |
每天中午12点 |
0 15 10 ? * MON-FRI |
工作日每天上午10:15 |
0 0/5 14 * * ? |
每天14:00~14:55,每5分钟一次 |
0 10,44 14 ? 3 WED |
3月的每个星期三的14:10和14:44 |
0 15 10 L * ? |
每月最后一天上午10:15 |
0 15 10 ? * 6#3 |
每月第三个星期五上午10:15 |
0 0 23 1/5 * ? |
从每月1号开始,每5天晚上11点 |
0 11 11 11 11 ? |
每年11月11日11:11(光棍节彩蛋) |
常见坑:
- 日字段和星期字段不能同时用
*,必须有一个用?表示"不指定" 0/5和*/5效果一样,都是从0开始每5单位- 年份一般省略,除非你非要限制到某几年