为什么 IsCancellationRequested 在停止 .NET Core 3.1 中的 BackgroundService 时未设置为 true?

Jim*_*Aho 7 c# application-lifecycle cancellation-token .net-core

我已经阅读了大部分关于.NET Core 3.1 中IHostApplicationLifetime和 CancellationToken 的文章,但我找不到这不起作用的原因。

我有一个简单的BackgroundService,如下所示:

    public class AnotherWorker : BackgroundService
    {
        private readonly IHostApplicationLifetime _hostApplicationLifetime;

        public AnotherWorker(IHostApplicationLifetime hostApplicationLifetime)
        {
            _hostApplicationLifetime = hostApplicationLifetime;
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine($"Process id: {Process.GetCurrentProcess().Id}");
            _hostApplicationLifetime.ApplicationStarted.Register(() => Console.WriteLine("Started"));
            _hostApplicationLifetime.ApplicationStopping.Register(() => Console.WriteLine("Stopping"));
            _hostApplicationLifetime.ApplicationStopped.Register(() => Console.WriteLine("Stopped"));

            return Task.CompletedTask;
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Console.WriteLine("Executing");
            return Task.CompletedTask;
        }

        public override async Task StopAsync(CancellationToken cancellationToken)
        {
        // This actually prints "Stop. IsCancellationRequested: False". Why?
            Console.WriteLine($"Stop. IsCancellationRequested: {cancellationToken.IsCancellationRequested}");
            await base.StopAsync(cancellationToken);
        }
    }
Run Code Online (Sandbox Code Playgroud)

默认添加了 ConsoleLifetime,它监听 Ctrl+C 和 SIGTERM 并通知 IHostApplicationLifetime。我猜 IHostApplicationLifetime 反过来应该取消所有 CancellationTokens?这是一篇关于这个主题的好文章。那么为什么上述代码片段的输出如下所示?

Hosting starting
Started
Hosting started
(sends SIGTERM with `kill -s TERM <process_id>`)
Applicationis shuting down...
Stop. IsCancellationRequested: False
Stopped
Hosting stopped
Run Code Online (Sandbox Code Playgroud)

我希望它记录 Stop. IsCancellationRequested: True

我希望能够将此令牌传递给其他服务调用,以便它们能够正常关闭。

Ste*_*ary 19

这里有很多不同的取消标记,以及几种不同的抽象(IHostApplicationLifetime, IHostedService, BackgroundService)。解开一切需要一段时间。您链接到的博客文章很棒,但没有详细介绍CancellationTokens。

首先,如果您打算使用BackgroundService,我建议您阅读代码。另外,我强烈建议不要覆盖StartAsyncand StopAsync; BackgroundService以非常特殊的方式使用这些。

IHostedService有两种方法。StartAsync启动服务运行(可能是异步的);它需要一个CancellationToken表示应该取消“开始”操作(我没有检查过,但我认为只有在应用程序几乎立即关闭时才会触发此令牌)。请注意,StartAsync需要在托管服务完成之前被视为处于“已启动”或“正在运行”状态。同样,StopAsync停止服务(可能是异步的)。StopAsync在应用程序开始正常关闭时调用。正常关闭期间有一个超时,之后应用程序开始其“我现在很认真”关闭。该CancellationToken用于StopAsync表示“正常”变为“我现在是认真的”的转变。所以' 在正常关闭超时窗口期间设置。

如果您使用BackgroundService的,而不是IHostedService直接(像大多数人一样),你会得到一个不同的 CancellationTokenExecuteAsync。这个BackgroundService.StopAsync被调用时设置的——即,当应用程序开始正常关闭时。因此,它大致相当于IHostApplicationLifetime.ApplicationStopping,但仅限于单个托管服务。您可以期望在BackgroundWorker.ExecuteAsync CancellationToken设置后不久IHostApplicationLifetime.ApplicationStopping设置。

请注意,所有这些CancellationTokens 代表不同的东西:

  • IHostedService.StartAsyncCancellationToken意思是“中止此服务的启动”。
  • IHostedService.StopAsyncCancellationToken意思是‘停止此服务,现在,你在宽限期出局’。
  • IHostApplicationLifetime.ApplicationStopping 意思是“整个应用程序的正常关闭序列已经开始;请大家停止你正在做的事情”。
    • 作为正常关闭序列的一部分,所有IHostedService.StopAsync方法都会被调用。
  • BackgroundService.ExecuteAsync's 的CancellationToken意思是“停止这项服务”。

一个有趣的注意事项是BackgroundService类型通常不会看到“我现在是认真的”信号;他们只看到“停止这项服务”的信号。这可能是因为由 a 表示的“我现在是认真的”信号CancellationToken有些令人困惑。

如果您查看的代码Host,则关闭序列在其关闭序列中使用了更多取消标记:

  1. IHost.StopAsync具有CancellationToken “停止不应该再优雅”的意思
  2. 然后它为正常超时周期启动一个CancellationToken基于超时的超时
  3. ......和其他链接CancellationToken被解雇,如果任何IHost.StopAsync令牌被解雇或者如果定时器经过。所以这也意味着“停止不应再优雅”。
  4. 接下来它调用IHostApplicationLifetime.StopApplication取消IHostApplicationLifetime.ApplicationStopping CancellationToken.
  5. 然后它为 each调用StopAsyncIHostedService,传递“停止不应再优雅”的标记。
    • 所有BackgroundService类型都有自己的CancellationTokenExecuteAsync在启动期间传递给),并且这些取消标记StopAsync.
  6. 最后,它调用IHostApplicationLifetime.NotifyStopped取消IHostApplicationLifetime.ApplicationStopped CancellationToken

我为“不再优雅”信号计数 3(一个传入,一个计时器,一个将这两个信号连接起来),加上 2 on IHostApplicationLifetime,每个加上 1 BackgroundService,用于5 + n关闭期间使用的总共取消令牌。:)