Blazor Server端订阅Quartz监听器事件未触发问题求助
嘿,我来帮你排查这个问题——你遇到的情况大概率是监听器实例不共享,再加上异步方法的写法有问题,我们一步步拆解解决:
1. 最核心的问题:Quartz和Blazor用的不是同一个监听器实例
你在ConfigureServices里把GlobalJobListener注册成了Scoped服务,但如果Quartz注册监听器的时候是直接new了一个新实例(比如scheduler.ListenerManager.AddJobListener(new GlobalJobListener("Global"));),那麻烦就来了:Blazor页面注入的是DI容器里的Scoped实例,而Quartz用的是它自己创建的另一个实例。你在Blazor里订阅的是前者的事件,但Quartz触发的是后者的事件,自然不会调用你的BeforeStart方法。
怎么修复?
确保Quartz使用的监听器来自DI容器:
- 如果你是手动配置Quartz,从DI获取实例再添加:
services.AddScoped<GlobalJobListener>(); services.AddQuartz(q => { // 先完成其他Quartz配置(比如添加Job、Trigger) // 从DI容器获取监听器实例 using var scope = services.BuildServiceProvider().CreateScope(); var listener = scope.ServiceProvider.GetRequiredService<GlobalJobListener>(); q.Listeners.Add(listener); });
- 更优雅的方式是让Quartz自动从DI解析监听器:
services.AddQuartz(q => { // 添加监听器,Quartz会自动从DI获取实例 q.AddJobListener<GlobalJobListener>(listenerName: "GlobalJobListener"); }); // 别忘了添加Quartz的HostedService services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; });
2. 异步方法的写法完全错了
你的JobToBeExecuted里手动创建Task、调用Start()再Wait(),这完全是冗余操作,还容易导致线程阻塞和上下文同步问题。
修正后的监听器方法
假设你的事件委托是同步的(比如public delegate void TaskExecution();),直接调用事件就行:
public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default) { // 直接触发事件,返回已完成的Task Started?.Invoke(); return Task.CompletedTask; } public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default) { Vetoed?.Invoke(); return Task.CompletedTask; } public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default) { Executed?.Invoke(jobException); return Task.CompletedTask; }
如果你的事件是异步委托(比如public delegate Task TaskExecution();),就用await调用:
public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default) { if (Started != null) { await Started.Invoke(); } }
3. 验证Blazor的订阅逻辑是否执行
你可以在OnInitializedAsync里加个日志,确认订阅逻辑真的跑了:
protected override async Task OnInitializedAsync() { // 注意这里如果scheduler是异步获取的,要加await scheduler = await quartz.Scheduler; if (scheduler != null) { Log.Information("已订阅JobListener的Started和Executed事件"); listener.Started += BeforeStart; listener.Executed += AfterEndAsync; } else { Log.Warning("Scheduler为null,没订阅到事件"); } }
4. 检查事件委托的定义
确保你的TaskExecution和TaskExecutionComplete委托和调用时的参数匹配,比如:
// 同步委托 public delegate void TaskExecution(); public delegate void TaskExecutionComplete(JobExecutionException exception); // 异步委托(如果需要异步处理事件) public delegate Task TaskExecution(); public delegate Task TaskExecutionComplete(JobExecutionException exception);
要是委托定义和调用参数不匹配,编译器可能没报错,但运行时事件就是触发不了。
内容的提问来源于stack exchange,提问作者Catalin Cernat

