Search code examples
c#.net-corebackground-serviceasp.net-core-hosted-services

BackgroundService not shutting down, stoppingToken never set with .net core generic host


I have a BackgroundService hosted in .net generic host as follows:

var builder = Host.CreateDefaultBuilder(args);

builder
    .ConfigureLogging((hostingContext, logging) =>
    {
        logging.ClearProviders();
        logging.AddConsole();
        if(hostingContext.HostingEnvironment.IsDevelopment() == true)
            logging.AddDebug();
    })
    .ConfigureHostConfiguration(configurationBuilder =>
    {
        configurationBuilder.AddCommandLine(args);
    })
    .ConfigureAppConfiguration((hostingContext, configApp) =>
    {
        var env = hostingContext.HostingEnvironment;
        Console.WriteLine(env.EnvironmentName);
    })
    .UseConsoleLifetime();

I then have my worker:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // here i am doing something with ClientWebSocket, threads etc
        // snipped for brevity. I want to avoid spinning in a loop

        // the debugger reaches the following line
        WaitHandle.WaitAny(new[] { stoppingToken.WaitHandle });

        // but never hits this line
        this.logger.LogInformation("shutting down worker");
    }
}

On ctrl+c from the windows terminal of the running app it says Application is shutting down (this is from the framework) however the stoppingToken never gets set (so i cant shutdown my worker).

How and when does stoppingToken get set, and how can i gracefully terminate my worker?

The console


Solution

  • For BackgroundServices, they need to continue running until the cancellation token says to stop. To do that, you need a while loop. However, when you pass this cancellation token into any Async method, it will stop that method from running all the way through the chain if you use that same token in all the layers. It should look something like this:

      protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                // here i am doing something with ClientWebSocket, threads etc
                // snipped for brevity. I want to avoid spinning in a loop
                ... 
                await client.DownloadAsync(..., stoppingToken);
                ...
            }
    
            // but never hits this line
            this.logger.LogInformation("shutting down worker");
        }