Quartz.NET CronTrigger未按时触发的根本原因是时区配置错误和调度器启动时机不当;需显式指定时区、确保Start()在添加所有job/trigger后调用、使用ISchedulerFactory获取调度器、job类须有public无参构造函数且非static或嵌套类。

Quartz.NET 的 CronTrigger 为什么没按时触发
根本原因通常是时区配置和调度器启动时机不对。默认情况下 CronTrigger 使用系统本地时区,但服务器部署在 Docker 或 Linux 上时,可能没有正确加载时区数据,导致表达式解析偏差——比如写的是 "0 0 2 * * ?"(每天凌晨2点),实际却按 UTC 时间跑,相当于北京时间上午10点。
- 显式指定时区:创建
CronScheduleBuilder时用.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("China Standard Time")) - 确保调度器已启动:
await scheduler.Start()必须在所有 job/trigger 添加完毕后调用,否则 trigger 注册了但不生效 - 避免在
ConfigureServices中直接 new Scheduler —— 应通过ISchedulerFactory.GetScheduler()获取,否则生命周期管理失效,应用重启后 job 丢失
Job 类必须继承 IJob 且无参构造函数不可少
Quartz.NET 用反射实例化 job,如果自定义 job 类没有 public 无参构造函数,会抛出 JobInstantiationFailedException,错误信息里带 “No suitable constructor found”。
- 不要写
public MyJob(ILogger<myjob> logger)</myjob>这种带参构造 —— Quartz 不支持 DI 自动注入到 job 实例中(除非配Microsoft.Extensions.DependencyInjection插件) - 正确做法是:保留无参构造,把依赖通过
JobDataMap或IScheduler.Context传入,或改用IJobFactory自定义工厂接管实例化 - job 类不能是
static,也不能是嵌套类(nested class),否则反射失败
“0 0/5 * * * ?” 这类表达式在 Quartz.NET 里要小心秒级精度
很多人以为 "0 0/5 * * * ?" 表示“每5分钟执行一次”,其实它表示“每小时的第0、5、10…55分钟的第0秒触发”,看起来没问题,但一旦 job 执行耗时超过 1 分钟,就可能被跳过或堆积——因为 Quartz 默认使用 SimpleThreadPool,线程数不够时新 trigger 会被丢弃(取决于 MisfireInstruction)。
- 检查 misfire 处理策略:用
.WithMisfireHandlingInstructionFireAndProceed()替代默认的SmartPolicy,避免漏跑 - 确认线程池大小:在
quartz.threadPool.threadCount配置中至少设为 3–5,尤其当多个 job 并发或单个 job 耗时波动大时 - 别依赖秒级 cron 做高频任务(如每10秒)——用
SimpleTrigger更稳,cron 在秒级场景下易受 GC 或调度延迟影响
ASP.NET Core 里怎么让 Quartz.NET 随应用启停自动管理
直接 new Scheduler 容易造成资源泄漏,且应用关闭时 job 可能被强制中断,留下脏状态。必须用 IHostedService 封装生命周期。
- 注册时用
services.AddQuartz(q => { ... })+services.AddQuartzHostedService()(Quartz.Extensions.Hosting 包提供) - 避免手动调用
Shutdown():HostedService 的StopAsync会自动 graceful shutdown,但需确保 job 内部不阻塞(比如没加cancellationToken检查) - 数据库持久化(
AdoJobStore)必须开启:否则应用重启后所有 trigger 丢失,哪怕用了 hosted service 也白搭
真正麻烦的不是写 cron 表达式,而是 job 执行中途崩溃、线程卡死、或数据库连接超时导致 trigger 状态滞留。这些不会报错,只会让调度越来越慢,直到某天突然全停——得盯日志里的 ERROR org.quartz.core.JobRunShell 和数据库 QRTZ_FIRED_TRIGGERS 表的残留记录。











