问题
I have faced a situation when Task.Delay()
method would trigger an event of cancellation in IApplicationLifetime
. Here is the code:
static async Task Main(string[] args)
{
Console.WriteLine("Starting");
await BuildWebHost(args)
.RunAsync();
Console.WriteLine("Press any key to exit..");
Console.ReadKey();
}
private static IHost BuildWebHost(string[] args)
{
var hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.AddEnvironmentVariables();
config.AddCommandLine(args);
})
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.SetBasePath(Directory.GetCurrentDirectory());
configApp.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<BrowserWorkerHostedService>();
services.AddHostedService<EmailWorkerHostedService>();
})
.UseConsoleLifetime();
return hostBuilder.Build();
}
and here are hosted services which are stopping abnormally:
public class BrowserWorkerHostedService : BackgroundService
{
private IApplicationLifetime _lifetime;
private IHost _host;
public BrowserWorkerHostedService(
IApplicationLifetime lifetime,
IHost host)
{
this._lifetime = lifetime;
this._host = host;
}
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!_lifetime.ApplicationStarted.IsCancellationRequested
&& !_lifetime.ApplicationStopping.IsCancellationRequested
&& !stopToken.IsCancellationRequested)
{
Console.WriteLine($"{nameof(BrowserWorkerHostedService)} is working. {DateTime.Now.ToString()}");
//lifetime.StopApplication();
//await StopAsync(stopToken);
await Task.Delay(1_000, stopToken);
}
Console.WriteLine($"End {nameof(BrowserWorkerHostedService)}");
await _host.StopAsync(stopToken);
}
}
public class EmailWorkerHostedService : BackgroundService
{
private IApplicationLifetime _lifetime;
private IHost _host = null;
public EmailWorkerHostedService(
IApplicationLifetime lifetime,
IHost host)
{
this._lifetime = lifetime;
this._host = host;
}
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!_lifetime.ApplicationStarted.IsCancellationRequested
&& !_lifetime.ApplicationStopping.IsCancellationRequested
&& !stopToken.IsCancellationRequested)
{
Console.WriteLine($"{nameof(EmailWorkerHostedService)} is working. {DateTime.Now.ToString()}");
await Task.Delay(1_000, stopToken);
}
Console.WriteLine($"End {nameof(EmailWorkerHostedService)}");
await _host.StopAsync(stopToken);
}
}
I would like my services to be running, unless lifetime.StopApplication()
is triggered. However, hosted services are stopped, because lifetime.ApplicationStarted.IsCancellationRequested
variable is set to true
upon a second itteration.. Even though, in theory, I have no code that explicitly aborts the application.
Log will look like this:
Starting BrowserWorkerHostedService is working. 09.07.2019 17:03:53
EmailWorkerHostedService is working. 09.07.2019 17:03:53
Application started. Press Ctrl+C to shut down.
Hosting environment: Production
Content root path: xxxx
End EmailWorkerHostedService
End BrowserWorkerHostedService
Is there a good explanation why Task.Delay()
triggers ApplicationStarted cancellation event?
回答1:
You are misusing IApplicationLifetime
events. Their purpose is to give you the ability to associate some action with them. For example you want to start message queue listening only when you application is fully started. You are going to do it like this:
_applicationLifetime.ApplicationStarted. Register(StartListenMq);
I think using CancellationTokens
here wasn't the best idea, but it the way it was implemented.
When you want to cancel your HostedService
you should check only token received in ExecuteAsync
method. The flow will look look this:
IApplicationLifetime.StopApplication()
=> will trigger IApplicationLifetime.ApplicationStopping
=> will trigger IHostedService.StopAsync()
=> will stopToken
And now for your question: why it happens on await Task.Delay()
? Look again at BackgroundService.StartAsync()
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
This code does't await ExecuteAsync
. At the moment you call async operation in your code StartAsync()
will continue to run. Somewhere in it's callstack it will trigger ApplicationStarted
and since you are listening to it you will get _lifetime.ApplicationStarted.IsCancellationRequested = true
.
来源:https://stackoverflow.com/questions/56955587/task-delay-triggers-cancellation-of-application-lifetime