☰
Quartz.NET SchedulerListener 完全指南:监听调度器级生命周期事件
2026/10/6 12:29:26 网站建设 项目流程
  • 任务调度
  • 后端

【免费下载链接】quartznet

Quartz Enterprise Scheduler .NET

项目地址:https://gitcode.com/gh_mirrors/qu/quartznet
点击查看免费下载

调度器监听器(SchedulerListener)是 Quartz.NET 中与ITriggerListener、IJobListener并列的第三类监听机制,但它监听的并非某个具体 Job 或 Trigger 的个体行为,而是调度器自身的生命周期与全局事件:Job/Trigger 的添加与移除、暂停与恢复、调度器启停与关闭、内部严重错误等。通过本文,你将掌握ISchedulerListener接口的完整回调语义、注册方式(直接注册与依赖注入两种路径)、内部触发链路,以及一个可直接落地的 ASP.NET Core 集成示例。

本指南源自 Quartz.NET 官方教程 Lesson 8,并对照当前仓库中 ISchedulerListener.cs、QuartzScheduler.cs 等源码展开纵深讲解,帮助你把文档知识落到实际调用链上。

什么是 SchedulerListener

SchedulerListener 与 Trigger/Job 监听器的最大区别在于关注粒度:

  • IJobListener:在 Job 执行前后收到JobToBeExecuted/JobWasExecuted通知,围绕"某一次 Job 执行";
  • ITriggerListener:在 Trigger 触发前后收到TriggerFired/TriggerComplete通知,围绕"某一次 Trigger 触发";
  • ISchedulerListener:收到的是调度器层面的结构性事件——Job 被调度、Trigger 被移除、Trigger 永远不会再触发、整组 Trigger/Job 被暂停或恢复、调度器出错、调度器关闭等,不必然与某个具体的 Trigger 或 Job 相关。

用官方文档(scheduler-listeners.md)的原话概括:SchedulerListener 接收的是"调度器自身内部发生的事件",而非绑定在特定 Trigger 或 Job 上的事件。

从源码结构看,ISchedulerListener定义在 src/Quartz/ISchedulerListener.cs,所有回调的第一个参数都是触发通知的IScheduler实例,这意味一个监听器实例可以在同一宿主内同时服务多个调度器,并通过参数区分究竟是哪个调度器暂停了 Trigger 或发生了故障。

ISchedulerListener 接口全解

接口演进:从旧版到当前仓库版本

官方教程(对应 Quartz 1.x 时代)展示的接口是同步风格,只有 9 个回调:

public interface ISchedulerListener { void JobScheduled(Trigger trigger); void JobUnscheduled(string triggerName, string triggerGroup); void TriggerFinalized(Trigger trigger); void TriggersPaused(string triggerName, string triggerGroup); void TriggersResumed(string triggerName, string triggerGroup); void JobsPaused(string jobName, string jobGroup); void JobsResumed(string jobName, string jobGroup); void SchedulerError(string msg, SchedulerException cause); void SchedulerShutdown(); }

而当前仓库(Quartz.NET 4.x)中的 ISchedulerListener.cs 已全面重构为异步ValueTask+ 默认实现(default interface methods)风格,回调数量大幅扩充,且每个方法都接收IScheduler scheduler与CancellationToken。这意味着:

  • 实现类只需覆写自己关心的事件,其余回调走默认空实现,不必像旧版那样为每个方法都写空壳;
  • 所有通知都是异步可取消的,与调度器内部的async工作流保持一致;
  • Name属性默认返回实现类型名,用于监听器的注册与移除标识。

当前仓库中的完整回调清单

按事件类别整理如下(方法签名以 ISchedulerListener.cs 为准):

事件类别回调方法触发时机
调度/移除JobScheduled一个 Job 被 Trigger 调度时(携带该ITrigger)
调度/移除JobUnscheduled一个 Trigger(及其绑定的调度)被移除时,携带TriggerKey
调度/移除JobAdded/JobDeletedJob 被加入 / 删除时,携带IJobDetail/JobKey
调度/移除SchedulingDataCleared所有 Job、Trigger、Calendar 被清空时
生命周期终结TriggerFinalizedTrigger 达到"永远不再触发"条件时
暂停/恢复TriggerPaused/TriggersPaused单个 / 整组 Trigger 被暂停
暂停/恢复TriggerResumed/TriggersResumed单个 / 整组 Trigger 被恢复
暂停/恢复JobPaused/JobsPaused单个 / 整组 Job 被暂停
暂停/恢复JobResumed/JobsResumed单个 / 整组 Job 被恢复
错误SchedulerError调度器内部发生严重错误(如 JobStore 反复失败、Job 实例化失败),携带SchedulerErrorContext
错误TriggerInError/TriggersInError单个 Trigger / 某 Job 的全部 Trigger 进入Error状态
中断JobInterrupted(两个重载)Job 被中断;带fireInstanceId的重载可精确到是哪一次执行被中断
启停SchedulerStarting/SchedulerStarted/SchedulerInStandbyMode调度器启动中 / 已启动 / 进入待机
启停SchedulerShuttingDown/SchedulerShutdown调度器开始关闭序列 / 已关闭

几个值得注意的细节(均有源码注释佐证):

  • SchedulerError携带SchedulerErrorContext(见 SchedulerErrorContext.cs),它封装了出错时调度器所知的 Trigger、Job 与 firing 上下文,监听器可以直接据此暂停肇事 Trigger,而无需从错误消息文本里手工解析 Key。
  • JobInterrupted的两个重载:带fireInstanceId的重载用于无DisallowConcurrentExecutionAttribute的 Job(同一时刻可能有多个 firing 并发执行);默认实现会转而调用仅带JobKey的旧重载,保证老代码继续可用。官方注释明确建议:二选一实现,不要两个都覆写。
  • TriggersPaused/JobsPaused的string?参数为 null 时表示"所有组"(对应暂停全部的操作)。
  • TriggerInError说明"发生了什么"而非"为什么":导致原因单独通过SchedulerError(例如JobInstantiationException)到达;也有些错误转换完全发生在 JobStore 侧(无法加载 Job 类型),调度器侧根本没有 cause。

注册与移除:三种可用方式

与 Job/Trigger 监听器不同,SchedulerListener没有全局与非全局之分——它天然就是调度器级别的,任何实现了ISchedulerListener的对象都可以注册。当前官方文档与源码均确认这一点。

方式一:通过 IListenerManager 直接注册(经典方式)

IScheduler暴露了ListenerManager属性(接口定义见 IListenerManager.cs),支持按名称注册、替换与移除:

// 注册:同名已存在则替换 scheduler.ListenerManager.AddSchedulerListener(myListener); // 按监听器的 Name 移除,返回是否成功移除 bool removed = scheduler.ListenerManager.RemoveSchedulerListener(myListener.Name); // 查询 IReadOnlyList<ISchedulerListener> all = scheduler.ListenerManager.GetSchedulerListeners(); ISchedulerListener? byName = scheduler.ListenerManager.GetSchedulerListener(name);

底层实现位于 ListenerManagerImpl.cs:监听器保存在一个按注册顺序排列的OrderedDictionary<string, ISchedulerListener>中(内部有锁保护,注册与移除是线程安全的)。同名注册会直接替换旧实例——这是当前仓库 ISchedulerListener.cs 中Name注释反复强调"当同一类型注册多个实例时必须覆写 Name"的原因:同名注册会相互覆盖,若不覆写 Name 则后来的实例会顶掉先前的实例。

注册时还会做形状校验(VerifyShape):由于所有回调都有默认实现,一个签名不符的同名公共方法也会编译通过、但实际不实现任何回调——这种"死方法"监听器会被拒绝并抛出SchedulerConfigException,而不是被静默挂载后永不调用。

方式二:通过 QuartzBuilder 配置(依赖注入 / 服务化方式)

在 DI 场景(Quartz.Extensions.DependencyInjection)中,QuartzBuilder提供了三个泛型重载(见 QuartzBuilder.cs):

// 由 DI 容器按类型创建(需要 PublicConstructors 可达性) q.AddSchedulerListener<MySchedulerListener>(); // 直接传入实例 q.AddSchedulerListener(new MySchedulerListener()); // 用工厂函数从 IServiceProvider 构建 q.AddSchedulerListener(sp => new MySchedulerListener(sp.GetRequiredService<ILogger<MySchedulerListener>>()));

初始化时,SchedulerContentInitializer.cs 会依次把注册项与"以普通 DI 服务形式注册的ISchedulerListener"装配进scheduler.ListenerManager,并做了去重处理——若同一实例既走 builder 注册又注册为 DI 服务,不会收到两次通知。

方式三:SchedulerEventPlugin(配置文件方式)

仓库还内置了基于配置文件注册的SchedulerEventPlugin(见 SchedulerEventPlugin.cs),可通过 quartz 属性文件为调度器装配监听器,适合非 DI 的传统配置场景。

内部触发链路:通知从哪来、往哪去

SchedulerListener 的通知并非由某个独立线程广播,而是由调度器主流程在事件发生的同步/异步路径上显式调用。核心分发点集中在 QuartzScheduler.cs,从源码 grep 出的调用点可以还原典型链路:

  • 调度事件:ScheduleJob成功后调用NotifySchedulerListenersScheduled(trigger, ...)(QuartzScheduler.cs、#L937、#L1554、#L1585 等多处),RescheduleJob则先NotifySchedulerListenersUnscheduled再NotifySchedulerListenersScheduled(#L1295-L1296);
  • 移除事件:UnscheduleJob、DeleteJob、Clear路径分别触发NotifySchedulerListenersUnscheduled(#L1166、#L1189、#L1210、#L1295、#L2125);
  • 暂停/恢复:PauseTrigger/PauseTriggers/PauseJob/PauseJobs触发对应NotifySchedulerListenersPausedTrigger(s)/PausedJob(s)(#L1602、#L1650、#L1696、#L1716 等),整组暂停时传入null表示所有组;恢复同理(#L1739-#L1866);
  • 关闭事件:Shutdown流程中调用NotifySchedulerListenersShutdown(#L780)。

此外,JobStore 侧的信号器(ISchedulerSignaler,见 ISchedulerSignaler.cs)与SchedulerSignalerImpl(SchedulerSignalerImpl.cs)负责把TriggerFinalized、JobDeleted、TriggerInError/TriggersInError、SchedulerError等"来自 JobStore 运行过程"的事件转发给调度器并最终广播给监听器。也就是说:凡是调度器会"感知"到的重大状态迁移,几乎都有一条对应的监听器通知路径,这为可观测性与运维自动化提供了统一入口。

实战示例:在 ASP.NET Core 中集成 SchedulerListener

仓库自带的可运行示例Quartz.Examples.AspNetCore提供了最直观的落地参考:

1. 实现监听器(SampleSchedulerListener.cs)——得益于默认接口方法,只需覆写关心的事件:

using Quartz.Listeners; namespace Quartz.Examples.AspNetCore; public class SampleSchedulerListener : ISchedulerListener { private readonly ILogger<SampleSchedulerListener> logger; public SampleSchedulerListener(ILogger<SampleSchedulerListener> logger) { this.logger = logger; } public ValueTask SchedulerStarted(IScheduler scheduler, CancellationToken cancellationToken = default) { logger.LogInformation("Observed start of scheduler {SchedulerName}", scheduler.SchedulerName); return default; } }

2. 在 DI 配置中注册(Startup.cs):

services.AddQuartz(q => { // ... 其他调度器配置 ... // add some listeners q.AddSchedulerListener<SampleSchedulerListener>(); q.AddJobListener<SampleJobListener>(GroupMatcher<JobKey>.GroupEquals(jobKey.Group)); q.AddTriggerListener<SampleTriggerListener>(); });

SampleSchedulerListener的构造函数注入了ILogger,AddSchedulerListener<T>()会通过 DI 容器解析并创建实例——这正是方式二中"按类型注册"的典型用法。启动应用后,调度器启动时该监听器便会打印对应的调度器名称日志,验证链路是否生效。

典型应用场景

基于接口语义与源码实现,SchedulerListener 适合承担以下职责:

  1. 调度拓扑可观测性:记录 Job/Trigger 的添加、移除、暂停、恢复全量变更,形成审计日志;
  2. 健康告警:SchedulerError捕获 JobStore 反复失败、Job 实例化失败等严重故障,联动告警系统;TriggerInError/TriggersInError可用于识别"卡死"在错误状态的 Trigger;
  3. 优雅停机与恢复编排:SchedulerStarting/SchedulerShutdown通知驱动依赖调度器的组件做初始化/清理;
  4. 多调度器路由:同一监听器服务多个调度器时,通过回调第一参数scheduler区分来源(这正是 ISchedulerListener.cs 注释所强调的设计意图);
  5. 触发生命周期收尾:TriggerFinalized(Trigger 永不再触发)后可做资源回收或业务补偿。

小结

SchedulerListener 是 Quartz.NET 中观察调度器全局状态的官方通道。相比 Job/Trigger 监听器,它没有全局/非全局之分,也没有 matcher 过滤——因为调度器级别的事件天然属于整个调度器。当前仓库版本将其演进为全异步、默认接口方法风格,并扩展了TriggerInError、JobInterrupted(fireInstanceId)、SchedulerStarting/ShuttingDown、SchedulingDataCleared等细粒度回调;无论走ListenerManager直接注册、QuartzBuilder依赖注入,还是SchedulerEventPlugin配置装配,最终都会汇入 ListenerManagerImpl.cs 的统一管理。理解这一层监听机制,是构建可靠调度运维体系的关键一步。

  • 任务调度
  • 后端

【免费下载链接】quartznet

Quartz Enterprise Scheduler .NET

项目地址:https://gitcode.com/gh_mirrors/qu/quartznet
点击查看免费下载

相关推荐

上一篇:Vulnserver漏洞服务器如何快速上手:缓冲区溢出学习完整指南
下一篇:IDM 激活脚本使用教程:1 条命令冻结 30 天试用,新手 3 分钟跑通

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询