为什么在 .NET Core 3.1 中停止 BackgroundService 时 IsCancellationRequested 未设置为 true?
Why is IsCancellationRequested not set to true on stopping a BackgroundService in .NET Core 3.1?
我已经阅读了我能找到的大多数关于 IHostApplicationLifetime 和 .NET Core 3.1 中的 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);
}
}
默认添加ConsoleLifetime,监听Ctrl+C和SIGTERM,通知IHostApplicationLifetime。我猜 IHostApplicationLifetime 反过来应该取消所有 CancellationTokens? Here's a good article on the subject。那么为什么上面代码片段的输出如下?
Hosting starting
Started
Hosting started
(sends SIGTERM with `kill -s TERM <process_id>`)
Applicationis shuting down...
Stop. IsCancellationRequested: False
Stopped
Hosting stopped
我希望它记录 Stop. IsCancellationRequested: True
我希望能够将此令牌传递给其他服务调用,以便它们能够正常关闭。
传递给 StopAsync
的 CancellationToken
指示 BackgroundService
是否必须执行正常关机或硬关机。
您使用 kill -s TERM
停止进程,因此它发送 SIGTERM
信号要求应用程序正常关闭。因此 IsCancellationRequested
属性 仍然是假的。
要将令牌传递给其他服务调用,您必须提供自己的 CancellationToken
。您可以使用 CancellationTokenSource
来管理令牌的创建和取消。
这里有很多不同的取消标记,以及几个不同的抽象(IHostApplicationLifetime
、IHostedService
、BackgroundService
)。理清一切都需要一段时间。您链接到的博客 post 很棒,但没有详细介绍 CancellationToken
。
首先,如果您要使用BackgroundService
,我推荐reading the code。另外,我强烈建议不要覆盖 StartAsync
和 StopAsync
; BackgroundService
以非常特殊的方式使用它们。
IHostedService
has two methods. StartAsync
starts the service running (possibly asynchronously); it takes a CancellationToken
that indicates the "start" operation should be cancelled (I haven't checked, but I assume this token is only triggered if the app is shutdown almost immediately). Note that StartAsync
needs to complete before the hosted service is considered in the "started" or "running" state. Similarly, StopAsync
stops the service (possibly asynchronously). StopAsync
is invoked when the application begins its graceful shutdown. There's a timeout for the graceful shutdown period, after which the application begins its "I'm serious now" shutdown. The CancellationToken
for StopAsync
表示从"graceful"过渡到"I'm serious now"。所以它 不是 在正常关机超时期间设置的 window。
如果你直接使用 BackgroundService
而不是 IHostedService
(就像大多数人那样),你会得到 不同的 CancellationToken
ExecuteAsync
。这个 is set when BackgroundService.StopAsync
is invoked - i.e., when the application has started its graceful shutdown. So it's roughly equivalent to IHostApplicationLifetime.ApplicationStopping
,但仅限于单个托管服务。您可以预期 BackgroundWorker.ExecuteAsync
CancellationToken
会在设置 IHostApplicationLifetime.ApplicationStopping
后不久设置。
请注意,所有这些 CancellationToken
代表不同的东西:
IHostedService.StartAsync
的CancellationToken
表示"abort the starting of this service".
IHostedService.StopAsync
的CancellationToken
表示"stop this service right now; you're out of the grace period".
IHostApplicationLifetime.ApplicationStopping
表示 "the graceful shutdown sequence for this entire application has started; everyone please stop what you are doing"。
- 作为正常关机序列的一部分,将调用所有
IHostedService.StopAsync
方法。
BackgroundService.ExecuteAsync
的CancellationToken
表示"stop this service".
一个有趣的注意事项是 BackgroundService
类型通常看不到 "I'm serious now" 信号;他们只看到 "stop this service" 信号。这可能是因为 CancellationToken
表示的 "I'm serious now" 信号有点令人困惑。
如果您查看 the code for Host
,关闭序列有 更多 个取消标记用于其关闭序列:
IHost.StopAsync
需要 CancellationToken
meaning "the stop should no longer be graceful".
- 然后starts a
CancellationToken
-based timeout for the graceful timeout period.
- ... 和 another linked
CancellationToken
如果 或者 IHost.StopAsync
令牌被触发或者计时器结束则被触发。所以这个也意味着 "the stop should no longer be graceful".
- 下一个calls
IHostApplicationLifetime.StopApplication
, which cancels the IHostApplicationLifetime.ApplicationStopping
CancellationToken
.
- 然后 invokes
StopAsync
for each IHostedService
,传递 "stop should no longer be graceful" 令牌。
- 所有
BackgroundService
类型都有自己的 CancellationToken
(在启动期间传递给 ExecuteAsync
),这些取消标记是 cancelled by StopAsync
.
- 最后,invokes
IHostApplicationLifetime.NotifyStopped
, which cancels the IHostApplicationLifetime.ApplicationStopped
CancellationToken
。
我为 "no longer graceful" 信号计数 3(一个传入,一个计时器,一个连接这两个信号),在 IHostApplicationLifetime
上加上 2,每个 BackgroundService
加上 1,关闭期间总共使用了 5 + n
个取消令牌。 :)
我已经阅读了我能找到的大多数关于 IHostApplicationLifetime 和 .NET Core 3.1 中的 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);
}
}
默认添加ConsoleLifetime,监听Ctrl+C和SIGTERM,通知IHostApplicationLifetime。我猜 IHostApplicationLifetime 反过来应该取消所有 CancellationTokens? Here's a good article on the subject。那么为什么上面代码片段的输出如下?
Hosting starting
Started
Hosting started
(sends SIGTERM with `kill -s TERM <process_id>`)
Applicationis shuting down...
Stop. IsCancellationRequested: False
Stopped
Hosting stopped
我希望它记录 Stop. IsCancellationRequested: True
我希望能够将此令牌传递给其他服务调用,以便它们能够正常关闭。
传递给 StopAsync
的 CancellationToken
指示 BackgroundService
是否必须执行正常关机或硬关机。
您使用 kill -s TERM
停止进程,因此它发送 SIGTERM
信号要求应用程序正常关闭。因此 IsCancellationRequested
属性 仍然是假的。
要将令牌传递给其他服务调用,您必须提供自己的 CancellationToken
。您可以使用 CancellationTokenSource
来管理令牌的创建和取消。
这里有很多不同的取消标记,以及几个不同的抽象(IHostApplicationLifetime
、IHostedService
、BackgroundService
)。理清一切都需要一段时间。您链接到的博客 post 很棒,但没有详细介绍 CancellationToken
。
首先,如果您要使用BackgroundService
,我推荐reading the code。另外,我强烈建议不要覆盖 StartAsync
和 StopAsync
; BackgroundService
以非常特殊的方式使用它们。
IHostedService
has two methods. StartAsync
starts the service running (possibly asynchronously); it takes a CancellationToken
that indicates the "start" operation should be cancelled (I haven't checked, but I assume this token is only triggered if the app is shutdown almost immediately). Note that StartAsync
needs to complete before the hosted service is considered in the "started" or "running" state. Similarly, StopAsync
stops the service (possibly asynchronously). StopAsync
is invoked when the application begins its graceful shutdown. There's a timeout for the graceful shutdown period, after which the application begins its "I'm serious now" shutdown. The CancellationToken
for StopAsync
表示从"graceful"过渡到"I'm serious now"。所以它 不是 在正常关机超时期间设置的 window。
如果你直接使用 BackgroundService
而不是 IHostedService
(就像大多数人那样),你会得到 不同的 CancellationToken
ExecuteAsync
。这个 is set when BackgroundService.StopAsync
is invoked - i.e., when the application has started its graceful shutdown. So it's roughly equivalent to IHostApplicationLifetime.ApplicationStopping
,但仅限于单个托管服务。您可以预期 BackgroundWorker.ExecuteAsync
CancellationToken
会在设置 IHostApplicationLifetime.ApplicationStopping
后不久设置。
请注意,所有这些 CancellationToken
代表不同的东西:
IHostedService.StartAsync
的CancellationToken
表示"abort the starting of this service".IHostedService.StopAsync
的CancellationToken
表示"stop this service right now; you're out of the grace period".IHostApplicationLifetime.ApplicationStopping
表示 "the graceful shutdown sequence for this entire application has started; everyone please stop what you are doing"。- 作为正常关机序列的一部分,将调用所有
IHostedService.StopAsync
方法。
- 作为正常关机序列的一部分,将调用所有
BackgroundService.ExecuteAsync
的CancellationToken
表示"stop this service".
一个有趣的注意事项是 BackgroundService
类型通常看不到 "I'm serious now" 信号;他们只看到 "stop this service" 信号。这可能是因为 CancellationToken
表示的 "I'm serious now" 信号有点令人困惑。
如果您查看 the code for Host
,关闭序列有 更多 个取消标记用于其关闭序列:
IHost.StopAsync
需要CancellationToken
meaning "the stop should no longer be graceful".- 然后starts a
CancellationToken
-based timeout for the graceful timeout period. - ... 和 another linked
CancellationToken
如果 或者IHost.StopAsync
令牌被触发或者计时器结束则被触发。所以这个也意味着 "the stop should no longer be graceful". - 下一个calls
IHostApplicationLifetime.StopApplication
, which cancels theIHostApplicationLifetime.ApplicationStopping
CancellationToken
. - 然后 invokes
StopAsync
for eachIHostedService
,传递 "stop should no longer be graceful" 令牌。- 所有
BackgroundService
类型都有自己的CancellationToken
(在启动期间传递给ExecuteAsync
),这些取消标记是 cancelled byStopAsync
.
- 所有
- 最后,invokes
IHostApplicationLifetime.NotifyStopped
, which cancels theIHostApplicationLifetime.ApplicationStopped
CancellationToken
。
我为 "no longer graceful" 信号计数 3(一个传入,一个计时器,一个连接这两个信号),在 IHostApplicationLifetime
上加上 2,每个 BackgroundService
加上 1,关闭期间总共使用了 5 + n
个取消令牌。 :)