I've read most articles I can find about IHostApplicationLifetime and CancellationToken's in .NET Core 3.1, but I cannot find a reason why this is not working.
I have a simple BackgroundService which look like the following:
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);
}
}
The ConsoleLifetime is added by default, which listens to Ctrl+C and SIGTERM and informs the IHostApplicationLifetime. I guess IHostApplicationLifetime in turn should then cancel all CancellationTokens? Here's a good article on the subject. So why is the output from the above code snippet the following?
Hosting starting
Started
Hosting started
(sends SIGTERM with `kill -s TERM <process_id>`)
Applicationis shuting down...
Stop. IsCancellationRequested: False
Stopped
Hosting stopped
I would expect it to log Stop. IsCancellationRequested: True
I want to be able to pass this token around, to other service calls, for them to have the capability to shutdown gracefully.
There are a lot of different cancellation tokens here, and several different abstractions (IHostApplicationLifetime
, IHostedService
, BackgroundService
). It takes a while to untangle everything. The blog post you linked to is fantastic, but doesn't go into detail on the CancellationToken
s.
First, if you're going to use BackgroundService
, I recommend reading the code. Also, I strongly recommend not overriding StartAsync
and StopAsync
; BackgroundService
uses these in a very particular way.
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
represents the transition from "graceful" to "I'm serious now". So it's not set during that graceful shutdown timeout window.
If you use BackgroundService
instead of IHostedService
directly (like most people do), you get a different CancellationToken
in ExecuteAsync
. This one 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
, but scoped to a single hosted service. You can expect the BackgroundWorker.ExecuteAsync
CancellationToken
to be set shortly after IHostApplicationLifetime.ApplicationStopping
is set.
Note that all of these CancellationToken
s represent something different:
IHostedService.StartAsync
's CancellationToken
means "abort the starting of this service".IHostedService.StopAsync
's CancellationToken
means "stop this service right now; you're out of the grace period".IHostApplicationLifetime.ApplicationStopping
means "the graceful shutdown sequence for this entire application has started; everyone please stop what you are doing".
IHostedService.StopAsync
methods are invoked.BackgroundService.ExecuteAsync
's CancellationToken
means "stop this service".An interesting note is that BackgroundService
types don't normally see the "I'm serious now" signal; they only see the "stop this service" signal. This is likely because the "I'm serious now" signal represented by a CancellationToken
is somewhat confusing.
If you look into the code for Host
, the shutdown sequence has even more cancellation tokens used in its shutdown sequence:
IHost.StopAsync
takes a CancellationToken
meaning "the stop should no longer be graceful".CancellationToken
-based timeout for the graceful timeout period.CancellationToken
that is fired if either the IHost.StopAsync
token is fired or if the timer elapsed. So this one also means "the stop should no longer be graceful".IHostApplicationLifetime.StopApplication
, which cancels the IHostApplicationLifetime.ApplicationStopping
CancellationToken
.StopAsync
for each IHostedService
, passing the "stop should no longer be graceful" token.
BackgroundService
types have their own CancellationToken
(which was passed to ExecuteAsync
during startup), and those cancellation tokens are cancelled by StopAsync
.IHostApplicationLifetime.NotifyStopped
, which cancels the IHostApplicationLifetime.ApplicationStopped
CancellationToken
.I count 3 for the "no longer graceful" signal (one passed in, one timer, and one linking those two), plus 2 on IHostApplicationLifetime
, plus 1 for each BackgroundService
, for a total of 5 + n
cancellation tokens used during shutdown. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With