- 任务调度
- 后端
【免费下载链接】quartznet
Quartz Enterprise Scheduler .NET
调度器监听器(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/JobDeleted | Job 被加入 / 删除时,携带IJobDetail/JobKey |
| 调度/移除 | SchedulingDataCleared | 所有 Job、Trigger、Calendar 被清空时 |
| 生命周期终结 | TriggerFinalized | Trigger 达到"永远不再触发"条件时 |
| 暂停/恢复 | 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 适合承担以下职责:
- 调度拓扑可观测性:记录 Job/Trigger 的添加、移除、暂停、恢复全量变更,形成审计日志;
- 健康告警:
SchedulerError捕获 JobStore 反复失败、Job 实例化失败等严重故障,联动告警系统;TriggerInError/TriggersInError可用于识别"卡死"在错误状态的 Trigger; - 优雅停机与恢复编排:
SchedulerStarting/SchedulerShutdown通知驱动依赖调度器的组件做初始化/清理; - 多调度器路由:同一监听器服务多个调度器时,通过回调第一参数
scheduler区分来源(这正是 ISchedulerListener.cs 注释所强调的设计意图); - 触发生命周期收尾:
TriggerFinalized(Trigger 永不再触发)后可做资源回收或业务补偿。
小结
SchedulerListener 是 Quartz.NET 中观察调度器全局状态的官方通道。相比 Job/Trigger 监听器,它没有全局/非全局之分,也没有 matcher 过滤——因为调度器级别的事件天然属于整个调度器。当前仓库版本将其演进为全异步、默认接口方法风格,并扩展了TriggerInError、JobInterrupted(fireInstanceId)、SchedulerStarting/ShuttingDown、SchedulingDataCleared等细粒度回调;无论走ListenerManager直接注册、QuartzBuilder依赖注入,还是SchedulerEventPlugin配置装配,最终都会汇入 ListenerManagerImpl.cs 的统一管理。理解这一层监听机制,是构建可靠调度运维体系的关键一步。
- 任务调度
- 后端
【免费下载链接】quartznet
Quartz Enterprise Scheduler .NET
相关推荐
Doctrine ORM 事件系统完全指南:生命周期事件、回调与监听器的深度解析
Doctrine ORM 事件系统完全指南:生命周期事件、回调与监听器的深度解析 导读 本文是 Doctrine ORM 事件系统的技术实战指南。Doctrin
数据库ORM后端OpenHarmony-TPC/ImageKnife回调机制:完整生命周期事件监听
OpenHarmony TPC/ImageKnife回调机制:完整生命周期事件监听 引言 在OpenHarmony应用开发中,图像加载是高频且关键的操作。传统的
OpenHarmony移动开发缓存Reflex 事件触发器(Event Triggers)完全指南:从生命周期事件到全局键盘监听
Reflex 事件触发器(Event Triggers)完全指南:从生命周期事件到全局键盘监听 事件触发器(Event Triggers)是 Reflex 中连
后端前端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考