Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is IHostedService.StopAsync called twice when calling IHost.StopAsync?

Tags:

c#

.net-core

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?

like image 506
Jesús López Avatar asked Aug 09 '19 08:08

Jesús López


1 Answers

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);
like image 87
Panagiotis Kanavos Avatar answered Nov 15 '22 06:11

Panagiotis Kanavos