Quartz Scheduler多任务无法同时执行问题排查
问题分析与解决方案
看起来你的Quartz定时任务只能跑一个的核心问题出在任务调度的注册逻辑上,咱们一步步拆解:
核心问题1:CustomQuartzHostedService只处理了单个任务元数据
你在Startup.cs里注册了两个JobMetadata单例,但CustomQuartzHostedService的构造函数只注入了一个JobMetadata实例。DI容器这种情况下只会返回其中一个(通常是先注册的那个),导致另一个任务完全没被调度。
核心问题2:JobFactory存在潜在空引用风险
你的CustomQuartzJobFactory.NewJob方法用了_serviceProvider.GetService,如果后续把Job的生命周期改成非单例(比如Scoped),这个方法会返回null,直接导致任务执行失败。应该用GetRequiredService来确保必须拿到实例。
修复后的代码实现
1. 修改CustomQuartzHostedService,支持多任务调度
把构造函数里的单个JobMetadata改成IEnumerable<JobMetadata>,然后遍历所有元数据来注册任务:
public class CustomQuartzHostedService : IHostedService { private readonly ISchedulerFactory schedulerFactory; private readonly IJobFactory jobFactory; private readonly IEnumerable<JobMetadata> jobMetadatas; // 改成集合 public CustomQuartzHostedService(ISchedulerFactory schedulerFactory, IEnumerable<JobMetadata> jobMetadatas, IJobFactory jobFactory) { this.schedulerFactory = schedulerFactory; this.jobMetadatas = jobMetadatas; this.jobFactory = jobFactory; } public IScheduler Scheduler { get; set; } public async Task StartAsync(CancellationToken cancellationToken) { Scheduler = await schedulerFactory.GetScheduler(); Scheduler.JobFactory = jobFactory; // 遍历所有任务元数据,逐个注册 foreach (var jobMetadata in jobMetadatas) { var job = CreateJob(jobMetadata); var trigger = CreateTrigger(jobMetadata); await Scheduler.ScheduleJob(job, trigger, cancellationToken); } await Scheduler.Start(cancellationToken); } public async Task StopAsync(CancellationToken cancellationToken) { await Scheduler?.Shutdown(cancellationToken); } private ITrigger CreateTrigger(JobMetadata jobMetadata) { return TriggerBuilder.Create() .WithIdentity(jobMetadata.JobId.ToString()) .WithCronSchedule(jobMetadata.CronExpression) .WithDescription($"{jobMetadata.JobName}") .Build(); } private IJobDetail CreateJob(JobMetadata jobMetadata) { return JobBuilder .Create(jobMetadata.JobType) .WithIdentity(jobMetadata.JobId.ToString()) .WithDescription($"{jobMetadata.JobName}") .Build(); } }
2. 修复CustomQuartzJobFactory的空引用问题
把GetService换成GetRequiredService,确保Job实例一定能被获取:
public class CustomQuartzJobFactory : IJobFactory { private readonly IServiceProvider _serviceProvider; public CustomQuartzJobFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IJob NewJob(TriggerFiredBundle triggerFiredBundle, IScheduler scheduler) { var jobDetail = triggerFiredBundle.JobDetail; // 用GetRequiredService替代GetService,避免空引用 return (IJob)_serviceProvider.GetRequiredService(jobDetail.JobType); } public void ReturnJob(IJob job) { // 如果是Scoped生命周期的Job,这里需要释放资源,单例的话可以留空 } }
3. 保持Startup.cs的注册逻辑不变
你原来的Startup.cs注册是对的,两个JobMetadata、两个Job单例都不用改:
services.AddSingleton<IJobFactory, CustomQuartzJobFactory>(); services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>(); services.AddSingleton<Job1>(); services.AddSingleton<Job2>(); services.AddSingleton(new JobMetadata(Guid.NewGuid(), typeof(Job1), "Job1 Defination", "0/10 * * ? * * *")); //每10秒执行一次 services.AddSingleton(new JobMetadata(Guid.NewGuid(), typeof(Job2), "Job2 Defination", "0/10 * * ? * * *")); //每10秒执行一次 services.AddHostedService<CustomQuartzHostedService>();
为什么这样改能解决问题?
- 现在
CustomQuartzHostedService会拿到所有注册的JobMetadata,每个任务都会被创建并调度,两个Job会按照各自的Cron表达式同时运行。 GetRequiredService确保了Job实例的获取不会失败,即使后续调整Job的生命周期(比如改成Scoped来访问数据库上下文),也能正常工作。
测试一下,你应该能看到预期的日志输出:
info: exampleProject.Api.Services.QuartzServices.Job1[0] Job1 is working...
info: exampleProject.Api.Services.QuartzServices.Job2[0] Job2 is working...
内容的提问来源于stack exchange,提问作者Can Güven
相关产品推荐
相关产品推荐

