Quartz.net Scheduler.Shutdown(true) 没有杀死工作

Quartz.net Scheduler.Shutdown(true) not killing jobs

我有一个 运行 石英工作并终止了我的 BackgroundService,出于某种原因尽管调用 scheduler.Shutdown(true) 工作仍然 运行。

即使在循环和中断作业时,程序也会在线程退出之前关闭。

除了我下面的代码,我是否会考虑编写自定义 IScheduler 以确保 运行s 作业在关机时停止?

这是我的IJob执行方法:

public async Task Execute(IJobExecutionContext context)
{
    var cancellationToken = context.CancellationToken;

    while (cancellationToken.IsCancellationRequested == false)
    {
        // Extension method so we catch TaskCancelled exceptions.
        await TaskDelay.Wait(1000, cancellationToken);
        Console.WriteLine("keep rollin, rollin, rollin...");
    }
    Console.WriteLine("Cleaning up.");
    await Task.Delay(1000);
    Console.WriteLine("Really going now.");
}

这是我的关闭循环(直接调用关闭不会中断任何 运行 作业):

internal class QuartzHostedService : IHostedService
{
    // These are set by snipped constructor.
    private readonly IJobSettings jobSettings;
    private readonly ILogger logger;
    private readonly IScheduler scheduler;

    private async Task AddJobsToScheduler(CancellationToken cancellationToken = default)
    {
        var schedule = SchedulerBuilder.Create();

        var downloadJob = JobBuilder.Create<StreamTickersJob>().Build();

        var downloadJobTrigger = TriggerBuilder
            .Create()
            .ForJob(downloadJob)
            .WithDailyTimeIntervalSchedule(
                x => x.InTimeZone(serviceTimeZone)
                    .OnEveryDay()
                    .StartingDailyAt(new TimeOfDay(8,0))
                    .EndingDailyAt(new TimeOfDay(9,0)))
            .Build();

        await this.scheduler.ScheduleJob(downloadJob, downloadJobTrigger, cancellationToken);
    }

    public QuartzHostedService(IJobSettings jobSettings, IScheduler scheduler, ILogger<QuartzHostedService> logger)
    {
        this.jobSettings = jobSettings;
        this.scheduler = scheduler;
        this.logger = logger;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        this.logger.LogInformation("Quartz started...");
        await AddJobsToScheduler(cancellationToken);
        await this.scheduler.Start(cancellationToken);
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await this.scheduler.PauseAll(cancellationToken);

        foreach (var job in await this.scheduler.GetCurrentlyExecutingJobs(cancellationToken))
        {
            this.logger.LogInformation($"Interrupting job {job.JobDetail}");
            await this.scheduler.Interrupt(job.JobDetail.Key, cancellationToken);

        }
        await this.scheduler.Shutdown(cancellationToken);
    }
}

我可以确认 IHost 没有突然终止我的应用程序(至少没有几秒钟的测试暂停),因为我在主程序的末尾设置了一个断点,如下所示:

public static void Main(string[] args)
{
    // Wrap IHost in using statement to ensure disposal within scope.
    using (var host = CreateHostBuilder(args)
                                .UseSerilog<Settings>(Settings.Name)
                                .UseConsoleLifetime()
                                .Build()
                                .UseSimpleInjector(container))
    {
        // Makes no difference if I shutdown jobs here.
        // var lifetime = container.GetInstance<IHostApplicationLifetime>();            
        // lifetime.ApplicationStarted.Register(async () => { });
        // lifetime.ApplicationStopping.Register(async () => { });

        var logger = container.GetInstance<ILogger<Program>>();

        try
        {
            host.Run();
        }
        catch (Exception ex)
        {
            logger.LogCritical(ex, ex.Message);
        }

        // We reach here, whilst Jobs are still running :(
        logger.LogDebug($"Finish {nameof(Main)}().");
    }
}

我还根据我在网上找到的内容添加了以下内容,但它仍然等待关机:

var props = new NameValueCollection
{
    {"quartz.scheduler.interruptJobsOnShutdownWithWait", "true"},
};

var scheduler = AsyncContext.Run(async () => await new StdSchedulerFactory(props).GetScheduler());

我延迟允许作业在下面终止的解决方法有效,但是太狡猾了 - 请告知我如何才能在没有脆弱的任意延迟的情况下正常工作:

public async Task StopAsync(CancellationToken cancellationToken)
{
    await this.scheduler.PauseAll(cancellationToken);
    foreach (var job in await this.scheduler.GetCurrentlyExecutingJobs(cancellationToken))
    {
        this.logger.LogInformation($"Interrupting job {job.JobDetail}");
        await this.scheduler.Interrupt(job.JobDetail.Key, cancellationToken);

    }
    await Task.Delay(3000);
    await this.scheduler.Shutdown(cancellationToken);
}

如果勾选source code of generic host, you'll find that on host shutdown it waits for a default shutdown timeout, which is 5 seconds。这意味着如果您的作业需要更多时间才能完成,主机将超时退出,应用程序也将退出。

此外,根据您的评论,调度程序必须配置为在关机时中断 运行 个作业:

var props = new NameValueCollection
{
    {"quartz.scheduler.interruptJobsOnShutdownWithWait", "true"},
};

var scheduler = AsyncContext.Run(async () => await new StdSchedulerFactory(props).GetScheduler());

并调用 waitForJobsToComplete 参数设置为 true 以关闭:

await this.scheduler.Shutdown(waitForJobsToComplete: true, cancellationToken);

确保调度程序仅在所有作业完成后退出。

为保证应用程序仅在所有作业中断并完成后退出,您可以在主机退出后启动关机:

public static Task Main(string[] args)
{
    using (var host = CreateHostBuilder(args)
        .UseSerilog<Settings>(Settings.Name)
        .UseConsoleLifetime()
        .Build()
        .UseSimpleInjector(container))
    {
        var logger = container.GetInstance<ILogger<Program>>();

        try
        {
            await host.RunAsync();

            var scheduller = container.GetInstance<IScheduler<Program>>();
            scheduller.Shutdown(true);
        }
        catch (Exception ex)
        {
            logger.LogCritical(ex, ex.Message);
        }

        logger.LogDebug($"Finish {nameof(Main)}().");
    }
}