Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I (gracefully) shut down a worker service from within itself?

I'm using the .NET Core 3.1 worker service template to build a Windows Service.

I gather that the basic flow should be handled within ExecuteAsync, roughly like so:

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    _Logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

                    await SomeMethodThatDoesTheWork(stoppingToken);
                }
                catch (Exception ex)
                {
                    _Logger.LogError(ex, "Global exception occurred. Will resume in a moment.");
                }

                await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
            }
        }

I'm trying to properly signal from within the process that I want the app to shut down.

When stopping the service, I assume StopAsync() gets called. This seems to work fine. So I thought await StopAsync() would do the trick, but afterwards, ExecuteAsync still keeps running (so I'm guessing StopAsync won't request cancellation, and you're not supposed to call it yourself).

So I amended my loop with another bool:

            while (!stoppingToken.IsCancellationRequested && !_ShuttingDown)

This does exit the loop, and never enters ExecuteAsync again. However, the app just keeps on running. According to the debugger, it just stays on host.Run().

How do I signal to the application host that I want to shut down?

like image 569
Sören Kuklau Avatar asked Dec 27 '19 10:12

Sören Kuklau


1 Answers

In ASP.NET Core, background services are independent from the application. It's possible, e.g., for services to finish and yet the application continues executing. If you want your application to exit when your background service finishes, then you'll need to wire that up yourself.

You can inject IHostApplicationLifetime into your service and then call IHostApplicationLifetime.StopApplication. Something like this:

public sealed class MyService : BackgroundService
{
  private readonly IHostApplicationLifetime _hostApplicationLifetime;
  private readonly ILogger<MyService> _logger;

  public MyService(IHostApplicationLifetime hostApplicationLifetime, ILogger<MyService> logger)
  {
    _hostApplicationLifetime = hostApplicationLifetime;
    _logger = logger;
  }

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    try
    {
      while (!stoppingToken.IsCancellationRequested)
      {
        try
        {
          _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
          await SomeMethodThatDoesTheWork(stoppingToken);
        }
        catch (Exception ex)
        {
          _logger.LogError(ex, "Global exception occurred. Will resume in a moment.");
        }

        await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
      }
    }
    finally
    {
      _logger.LogCritical("Exiting application...");
      _hostApplicationLifetime.StopApplication();
    }
  }
}
like image 149
Stephen Cleary Avatar answered Nov 09 '22 08:11

Stephen Cleary