【问题标题】:Quartz.net Scheduler.Shutdown(true) not killing jobsQuartz.net Scheduler.Shutdown(true) 不杀死作业
【发布时间】:2021-06-27 17:12:31
【问题描述】:

我有一个正在运行的石英作业并终止了我的BackgroundService,尽管调用了scheduler.Shutdown(true),但该作业仍在运行。

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

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

这是我的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);
}

【问题讨论】:

  • 您好,我建议将 graceful IHostedService shutdown 添加到您的BackgroundService。你可以找到很棒的article。或者尝试更简单的方法,在Startup.cs 中添加优雅关机。在您的方法 Configure - IHostApplicationLifetime lifetime 中添加依赖。并注册 ApplicationStopping 事件 - lifetime.ApplicationStopping.Register(() =&gt; { Task.Delay(TimeSpan.FromSeconds(10)).GetAwaiter().GetResult(); }); 我认为 10 秒足以让 Quartz 停止所有工作。
  • 感谢@DarkSideMoon,我发布了一个受您的 cmets 启发的答案,谢谢。我仍然必须创建 StopAllJobs 函数,但这似乎至少在我的用例中有效,并允许关闭作业。
  • 在异步方法中使用 await Task.Delay(2000) 而不是 Thread.Sleep(2000)
  • @DarkSideMoon 如果您以异步同步方式调用Task.Delay,为什么不Thread.Sleep.GetAwaiter().GetResult() 尚未完成 Task 杀死了它的异步特性。
  • 感谢已更新使用Task.Delay()。同样的问题,工人没有关闭,也没有等待终止。

标签: c# quartz.net


【解决方案1】:

如果您检查source code of generic host,您会发现在主机关闭时它会等待default shutdown timeout,即5 seconds。这意味着如果您的作业需要更多时间才能完成,主机将超时退出,应用程序也将退出。

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

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

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

并使用设置为truewaitForJobsToComplete 参数调用以进行关机:

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)}().");
    }
}

【讨论】:

  • 谢谢Andrii,我已经排除了关机超时,我添加了几秒钟的延迟(5秒内),并且可以确认Quartz立即完成关机功能而无需等待。这不是.net core IHost 被终止,这绝对是quartz.net 问题(或者我对它的误解)。
  • @morleyc 在这种情况下,您需要调用await this.scheduler.Shutdown(true, cancellationToken); 以确保在所有作业停止之前关闭方法不会退出。
  • 谢谢 - 结合我在问题的倒数第二个代码块中提到的 props 和 {"quartz.scheduler.interruptJobsOnShutdownWithWait", "true"} 设置,然后就可以了,谢谢。无需任何循环只需致电await this.scheduler.Shutdown(true, cancellationToken); 如果您可以更新您的答案以包括这两个方面,我将奖励:)
  • 我已经更新了答案,感谢interruptJobsOnShutdownWithWait 的提示。我不知道。
  • 非常感谢 - 明天将奖励赏金它不允许我在发布后这么快奖励。感谢您抽出宝贵时间回复并解决问题。
猜你喜欢
  • 2019-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-09
  • 1970-01-01
相关资源
最近更新 更多