运行中Razor Pages应用添加Quartz Job&Trigger空引用问题
Quartz.NET Razor Pages 动态作业调度问题修复方案
问题背景
- 基于Quartz.NET + MySQL持久化实现Razor Pages作业调度应用,基础固定作业调度、数据库连接、持久化功能运行正常
- 核心需求:不重启应用的前提下,根据用户输入动态新增、调度作业
- 当前异常:跨类访问调度器实例时抛出空引用异常,动态调度功能失效
- 已尝试方案:手动存储IScheduler实例供跨类调用、考虑过常驻轮询作业/启停新应用实例的方案,但均存在问题
核心问题根因
- 空引用异常直接原因:
ScheduleStore是普通类未做单例处理,初始化调度器时你手动new了一个ScheduleStore实例并存入调度器引用,但后续在RunningScheduler中又new了一个全新的ScheduleStore实例,这个新实例的tempScheduler属性从未被赋值,默认值为null,调用时必然抛出空引用 - 无效逻辑bug:初始化代码中先通过
var key = new JobKey("Notify Job", "DEFAULT")创建JobKey实例,立刻判断if (key == null),该判断永远为false,分支内的作业添加逻辑永远不会执行 - 托管服务使用错误:
RunningScheduler实现了IHostedService接口,这类服务是应用启动时由DI容器自动创建并执行的,你手动传入JobMetadata参数实例化的对象不会被托管,StartAsync方法的执行生命周期完全不受控,不适合用来处理动态作业新增逻辑 - 冗余设计:不需要自行维护调度器实例的静态/全局存储,Quartz.NET官方提供的
ISchedulerFactory默认注册为单例,任何位置通过工厂获取的都是同一个IScheduler实例,完全符合「不初始化多个调度器实例」的要求
优雅实现方案(无需轮询、无需重启应用)
不需要实现常驻扫描作业,也不需要启停新应用实例,直接通过依赖注入获取调度器实例,在用户提交配置的接口/页面处理逻辑中直接动态添加作业即可。
1. 修正调度器初始化逻辑
删除自定义的ScheduleStore相关代码,初始化时直接启动调度器即可:
// 程序启动时的初始化逻辑(通常在Program.cs中配置) var scheduler = await schedulerFactory.GetScheduler(); scheduler.JobFactory = jobFactory; // 移除永远不成立的key==null判断,启动时需要加载的固定作业正常添加即可 await scheduler.Start(cancellationToken); // 不需要手动new ScheduleStore存储实例,ISchedulerFactory本身就是单例
2. 动态添加作业的实现
在需要处理用户新增作业请求的Razor Pages PageModel、接口服务中,直接注入ISchedulerFactory获取调度器实例,执行动态调度:
public class AddJobPageModel : PageModel { private readonly ISchedulerFactory _schedulerFactory; // 构造函数注入调度器工厂,由DI容器自动提供实例 public AddJobPageModel(ISchedulerFactory schedulerFactory) { _schedulerFactory = schedulerFactory; } public async Task<IActionResult> OnPostAsync(JobMetadata jobMetaData) { // 从工厂获取调度器实例,全局唯一,不会出现多实例问题 var scheduler = await _schedulerFactory.GetScheduler(); // 创建作业实例 IJobDetail jobDetail = CreateJob(jobMetaData); // 根据配置创建对应触发器 ITrigger trigger; if (jobMetaData.CronExpression == "--") { trigger = CreateSimpleTrigger(jobMetaData); } else { trigger = CreateCronTrigger(jobMetaData); } // 动态调度作业,立即生效,不需要重启应用 await scheduler.ScheduleJob(jobDetail, trigger); return RedirectToPage("Index"); } }
3. 注意事项
- 不要手动
new依赖注入容器管理的服务实例,所有需要用到调度器、调度工厂的地方,都通过构造函数注入获取 - 不需要周期性扫描数据库,用户提交作业配置时直接调用调度接口添加即可,性能远高于轮询方案
- 因为你已经配置了MySQL持久化,动态添加的作业会自动持久化到数据库,应用重启后会自动加载,不需要额外处理
- 每次新增作业时启停新应用实例的方案完全不可行,会造成严重的资源浪费和实例冲突,不要采用
如果你需要在非请求处理的后台逻辑中动态操作作业,也可以通过同样的注入方式获取IScheduler实例,所有操作都是线程安全的。
内容的提问来源于stack exchange,提问作者Jat
相关产品推荐
相关产品推荐

