Quartz.NET 调度框架:从入门到封装实战

说实话,第一次接触 Quartz.NET 时我有点懵------一堆接口、调度器、触发器,还有那让人头疼的 Cron 表达式。但熬过那个坎之后,它成了我最信任的任务调度工具。

它适合什么场景?

  • 企业级复杂调度:比如需要每年、每月、特定星期几执行的任务
  • 需要持久化和集群:任务跑一半服务器挂了?Quartz 可以恢复继续跑
  • 秒级精准调度:能精确到秒,且支持事务
  • 分布式环境:多台机器协同干活,任务不重复

需要留意的几点

  1. 学习曲线确实陡------比 Hangfire 和 FluentScheduler 复杂不少
  2. 如果想可靠,得配持久化存储(数据库)
  3. 集群配置挺折腾,得踩几个坑才能跑顺
  4. 简单场景别用它------杀鸡用牛刀,自己找麻烦

快速上手:三步跑起来

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单位
  • 年份一般省略,除非你非要限制到某几年
相关推荐
Oneslide17 小时前
机械革命 单系统纯净重装Ubuntu(全盘覆盖,清空原有Windows)
后端
GetcharZp17 小时前
告别OOM!用Go+libvips实现30000×50000超大图片的流式瓦片服务
后端·go
IT_陈寒17 小时前
JavaScript项目实战经验分享
前端·人工智能·后端
用户479492835691518 小时前
6w star,GitHub 趋势第一的 Ponytail,这个agent插件到底在火什么
前端·后端
神奇小汤圆19 小时前
2026一线大厂Java八股文精选(附答案,高质量整理)
后端
Warson_L20 小时前
LangGraph入门学习资料
后端
神奇小汤圆20 小时前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
后端
kfaino20 小时前
码农的AI翻身(四)你好,我叫 Attention
人工智能·后端
lwx5728020 小时前
探秘InnoDB:搞懂它的内存、线程、磁盘与日志刷盘策略
java·后端
云技纵横1 天前
Spring Boot Actuator 被打穿:线上开了这些端点,等于裸奔
后端