Given the following .NET Core 2.2 console application that uses generic host:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace SimpleGenericHost
{
class SimpleHostedService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Service started");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Service stopped");
return Task.CompletedTask;
}
}
class Program
{
static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureServices(services =>
{
services.AddHostedService<SimpleHostedService>();
})
.Build();
var runTask = host.RunAsync();
await Task.Delay(5000);
await host.StopAsync();
await runTask;
}
}
}
When you run it, the following is output:
Service started
Application started. Press Ctrl+C to shut down.
Hosting environment: Production
Content root path: C:\projects\ConsoleApps\SimpleGenericHost\bin\Debug\netcoreapp2.2\
Service stopped
Service stopped
As you can see SimpleHostedService.StopAsync
is called twice. Why?
Is this expected? am I missing something? Is there another way to stop the host that IHostedService.StopAsync
is called just once?
Because it was called twice - once after the delay and another time when the service actually stopped. It's not meant to be called when RunAsync
is used.
To stop after a timeout, use a CancellationTokenSource with a timeout and pass its token to RunAsync
:
var timeoutCts=new CancellationTokenSource(5000);
await host.RunAsync(timeoutCts.Token);
Explanation
StopAsync doesn't stop the service, it's used to notify the service that it needs to stop. It's called by the hosted service infrastructure itself when the application stops.
.NET Core is open source which means you can check the RunAsync source. RunAsync
starts the host and then awaits for termination:
await host.StartAsync(token);
await host.WaitForShutdownAsync(token);
The WaitForShutdownAsync method listens for a termination request, either from the console or by an explicit call to IHostApplicationLifetime.StopApplication
. When that happens it calls StopAsync
itself :
await waitForStop.Task;
// Host will use its default ShutdownTimeout if none is specified.
await host.StopAsync();
You should use StartAsync
instead of RunAsync
if you intend to manage the application's lifetime yourself.
In this case though, you only need to stop the application if it times out. You can do that easily by passing a cancellation token to RunAsync
that fires only after a timeout , through the CancellationTokenSource(int) constructor :
var timeoutCts=new CancellationTokenSource(5000);
await host.RunAsync(timeoutCts.Token);
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