Quartz.NET作业调度异常:启动作业后无法通过API停止指定作业
问题描述
需要实现以下功能:
- 调用Start API并传入Job ID时,调度一个Quartz作业,该作业从1开始每隔10秒打印数字
- 调用Stop API并传入相同Job ID时,停止该作业
测试时出现异常:执行Start方法后,_schedulers字典已存储对应Job ID的调度器,但调用Stop API时,_schedulers字典为空,抛出Job with ID: id1 not found异常。
相关代码
JobScheduler类
using Quartz; using Quartz.Impl; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; public class JobScheduler { public Dictionary<string, IScheduler> _schedulers; private bool _isSchedulerStarted; public JobScheduler() { _schedulers = new Dictionary<string, IScheduler>(); _isSchedulerStarted = false; } public async Task Start(string jobId, int startIndex) { try { IScheduler scheduler = await new StdSchedulerFactory().GetScheduler(); await scheduler.Start(); _isSchedulerStarted = true; if (_schedulers.ContainsKey(jobId)) { throw new ArgumentException($"A job with ID '{jobId}' already exists."); } else { IJobDetail job = JobBuilder.Create<PrintNumbersJob>() .WithIdentity(jobId, "group1") .UsingJobData("startIndex", startIndex) .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity($"{jobId}_Trigger", "group1") .StartNow() .Build(); await scheduler.ScheduleJob(job, trigger); _schedulers.Add(jobId, scheduler); Console.WriteLine(_schedulers); } Console.WriteLine("Schedulers before starting job:"); foreach (var kvp in _schedulers) { Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}"); } } catch (SchedulerException ex) { Console.WriteLine($"Error starting scheduler for job ID {jobId}: {ex.Message}"); _isSchedulerStarted = false; throw; } } public bool IsSchedulerStarted() { return _isSchedulerStarted; } public async Task Stop(string jobId) { try { Console.WriteLine("Schedulers before stopping job:"); foreach (var kvp in _schedulers) { Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}"); } if (IsSchedulerStarted()) { IScheduler scheduler = _schedulers[jobId]; Console.WriteLine("YES"); await scheduler.Shutdown(); _schedulers.Remove(jobId); Console.WriteLine($"Job with ID: {jobId} stopped successfully."); } else { throw new ArgumentException($"Job with ID: {jobId} not found."); } } catch (Exception ex) { Console.WriteLine($"Error stopping job with job ID {jobId}: {ex.Message}"); throw; } } }
Job控制器
using Microsoft.AspNetCore.Mvc; using System; using System.Threading.Tasks; using trash.Models; [ApiController] [Route("[controller]")] public class JobController : ControllerBase { private JobScheduler _jobScheduler; public JobController(JobScheduler jobScheduler) { _jobScheduler = jobScheduler; } [HttpPost("start")] public async Task<IActionResult> Start([FromBody] JobModel request) { await _jobScheduler.Start(request.JobId, 1); return Ok($"Job scheduled successfully with ID: {request.JobId}!"); } [HttpPost("stop")] public async Task<IActionResult> Stop([FromBody] JobModel request) { await _jobScheduler.Stop(request.JobId); return Ok($"Job with ID: {request.JobId} stopped successfully!"); } }
PrintNumbersJob作业
using Quartz; using System; using System.Threading.Tasks; public class PrintNumbersJob : IJob { private int _currentIndex; public Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; _currentIndex = dataMap.GetInt("startIndex"); while (true) { Console.WriteLine($"{context.JobDetail.Key} "+_currentIndex++); Task.Delay(TimeSpan.FromSeconds(10)).Wait(); } } }
程序输出
Schedulers before starting job: Key: id1, Value: Quartz.Impl.StdScheduler group1.id1 1 group1.id1 2 group1.id1 3 group1.id1 4 Schedulers before stopping job: Exception thrown: 'System.ArgumentException' in trash.dll Error stopping job with job ID id1: Job with ID: id1 not found.
问题原因分析
- 依赖注入生命周期错误:如果
JobScheduler在ASP.NET Core中注册为瞬时(Transient)或范围(Scoped)服务,Start和Stop请求会拿到不同的JobScheduler实例。Start请求往实例A的_schedulers添加数据,Stop请求用的是实例B,自然找不到对应Job ID。 - 作业执行阻塞线程:
PrintNumbersJob的Execute方法用while(true)+Task.Delay().Wait()同步阻塞,会占用Quartz的作业线程,可能导致后续调度操作异常。 - 状态判断逻辑不合理:全局的
_isSchedulerStarted变量无法准确反映单个作业的状态,且多线程环境下存在线程安全问题。
修复方案
1. 修正依赖注入生命周期
在Program.cs中将JobScheduler注册为单例服务,确保整个应用只有一个实例:
builder.Services.AddSingleton<JobScheduler>();
2. 修复作业阻塞问题
将作业的同步阻塞改为异步等待,利用Quartz的取消令牌实现优雅停止:
public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; int currentIndex = dataMap.GetInt("startIndex"); while (!context.CancellationToken.IsCancellationRequested) { Console.WriteLine($"{context.JobDetail.Key} {currentIndex++}"); await Task.Delay(TimeSpan.FromSeconds(10), context.CancellationToken); } }
3. 优化Stop方法的判断逻辑
去掉全局状态依赖,直接检查_schedulers中是否存在目标Job ID,并添加线程安全锁:
public class JobScheduler { private readonly Dictionary<string, IScheduler> _schedulers; private readonly object _lockObj = new object(); public JobScheduler() { _schedulers = new Dictionary<string, IScheduler>(); } public async Task Start(string jobId, int startIndex) { try { IScheduler scheduler = await new StdSchedulerFactory().GetScheduler(); await scheduler.Start(); lock (_lockObj) { if (_schedulers.ContainsKey(jobId)) { throw new ArgumentException($"A job with ID '{jobId}' already exists."); } } IJobDetail job = JobBuilder.Create<PrintNumbersJob>() .WithIdentity(jobId, "group1") .UsingJobData("startIndex", startIndex) .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity($"{jobId}_Trigger", "group1") .StartNow() .Build(); await scheduler.ScheduleJob(job, trigger); lock (_lockObj) { _schedulers.Add(jobId, scheduler); } Console.WriteLine("Schedulers after starting job:"); foreach (var kvp in _schedulers) { Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}"); } } catch (SchedulerException ex) { Console.WriteLine($"Error starting scheduler for job ID {jobId}: {ex.Message}"); throw; } } public async Task Stop(string jobId) { try { Console.WriteLine("Schedulers before stopping job:"); lock (_lockObj) { foreach (var kvp in _schedulers) { Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}"); } if (_schedulers.TryGetValue(jobId, out IScheduler scheduler)) { await scheduler.Shutdown(); _schedulers.Remove(jobId); Console.WriteLine($"Job with ID: {jobId} stopped successfully."); } else { throw new ArgumentException($"Job with ID: {jobId} not found."); } } } catch (Exception ex) { Console.WriteLine($"Error stopping job with job ID {jobId}: {ex.Message}"); throw; } } }
内容的提问来源于stack exchange,提问作者its_shyam640
相关产品推荐
相关产品推荐

