You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 13:57:53