quartz.net不可用timer替代,因其支持cron表达式调度、持久化、集群容错等生产级能力;需调用start()启动调度器,生产环境须用adojobstore;crontrigger应显式设utc时区;参数通过jobdatamap传递;任务依赖需手动触发或监听器实现;集群重复执行需正确配置并保障幂等性。

Timer 替代?
因为 Timer 只能做简单间隔触发,不支持表达式调度(如“每周一凌晨2点”)、任务持久化、集群容错、暂停/恢复/中断等生产级能力。Quartz.NET 的核心价值在于它把调度逻辑和业务逻辑解耦,并通过 IScheduler 统一管理生命周期。
常见踩坑点:StdSchedulerFactory.GetDefaultScheduler() 返回的是未启动的调度器,必须显式调用 Start() 才会真正开始触发任务;另外,若使用默认内存存储(RAMJobStore),应用重启后所有任务丢失——生产环境务必配 AdoJobStore 并连数据库。
复杂定时往往不是固定间隔,而是依赖 Cron 表达式或多个触发条件组合。Quartz.NET 中,CronTrigger 是最常用的选择,但要注意:它的时区默认是系统本地时区,跨服务器部署时容易出错,建议显式指定 UTC:
|
|
如果需要动态传参给任务(比如不同租户不同执行逻辑),不要在 IJob 构造函数里硬编码,而是用 JobDataMap:
job.SetJobData(new JobDataMap { ["tenantId"] = "t-123", ["env"] = "prod" });Execute(IJobExecutionContext context) 中取:context.JobDetail.JobDataMap.GetString("tenantId")
注意:JobDataMap 只支持可序列化类型,DateTimeOffset 可以,但 SqlConnection 或委托不行。
Quartz.NET 本身不提供原生的“任务A完成后才触发任务B”机制,但可通过以下方式模拟:
Execute() 结尾手动触发任务B:await scheduler.TriggerJob(new JobKey("job-b"));JobListener 监听任务A的成功完成事件,再触发任务B(更解耦)
不推荐用 Thread.Sleep() 等待或轮询,这会阻塞线程池;也不建议在监听器中做耗时操作,否则拖慢整个调度线程。若依赖链较长(A→B→C→D),应考虑改用工作流引擎(如 ESB 或 Dapr)而非硬塞进 Quartz。
典型现象:同一任务在多个节点上同时跑,日志里看到两条几乎完全一致的执行记录。根本原因通常是没正确配置集群相关属性,或数据库表未初始化。
必须检查三点:
quartz.jobStore.type 设为 Quartz.Impl.AdoJobStore.JobStoreTX, Quartz,且 quartz.jobStore.clustered 设为 truetables_sqlserver.sql),特别注意 QRTZ_LOCKS 表必须存在quartz.scheduler.instanceId 不能全设为 AUTO —— 若网络不稳定,可能生成重复 ID;建议用唯一标识如 HOSTNAME+PID集群下,Quartz 会自动选举一个节点作为“主调度器”,其他节点只做备份。但如果数据库连接超时或锁表失败,仍可能短暂出现双触发——这是分布式系统的固有边界,业务代码需具备幂等性。
真正难处理的,是那些无法轻易幂等化的任务,比如发短信、扣库存。这时候得靠外部协调服务或数据库行锁兜底,Quartz 自身不解决这个问题。
版权声明: 本站资源均来自互联网或会员发布,如果侵犯了您的权益请与我们联系,我们将在24小时内删除!谢谢!联系QQ:76900276