1

See the example code below. I would expect that the host automatically stops when the background service crashes, but this is not the case. The result is that the Windows service appears to be running but it doesn't perform any work... How can I make sure the Program detects that the background service has stopped?

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    
    namespace WorkerTest
    {
        public static class Program
        {
            public static void Main(string[] args)
            {
                CreateHostBuilder(args).Build().Run();
            }
    
            private static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .UseWindowsService()
                    .ConfigureServices((hostContext, services) =>
                    {
                        services.AddHostedService<Worker>();
                    });
        }
    
        public class Worker : BackgroundService
        {
            private readonly ILogger<Worker> _logger;
            private int _counter;
    
            public Worker(ILogger<Worker> logger)
            {
                _logger = logger;
            }
    
            protected override async Task ExecuteAsync(CancellationToken stoppingToken)
            {
                try
                {
                    while (!stoppingToken.IsCancellationRequested)
                    {
                        _logger.LogInformation("Worker running at: {time} ({counter})", DateTimeOffset.Now, _counter++);
                        if (_counter > 10)
                            throw new Exception("Something happened!");
                        await Task.Delay(1000, stoppingToken).ConfigureAwait(false);
                    }
                }
                finally
                {
                    if (!stoppingToken.IsCancellationRequested)
                        _logger.LogError("Worker stopped unexpectedly");
                }
            }
        }
    
    }

Marc Selis
  • 833
  • 12
  • 17

2 Answers2

3

You could run the following to ensure the service shuts down as expected:

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            try
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    _logger.LogInformation("Worker running at: {time} ({counter})", DateTimeOffset.Now, _counter++);
                    if (_counter > 10)
                        throw new Exception("Something happened!");
                    await Task.Delay(1000, stoppingToken).ConfigureAwait(false);
                }
            }
            catch (Exception)
            {
                 _applicationLifeTime.StopApplication();
            }
            finally
            {
                if (!stoppingToken.IsCancellationRequested)
                    _logger.LogError("Worker stopped unexpectedly");
                   
            }
        }

For this to work you will need to inject IApplicationLifetime into the BackgroundService

nik0lai
  • 2,585
  • 23
  • 37
  • That works, but I would have tought that the .NET Core Host would monitor the Task that is returned by ExecuteAsync as the [documentation](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.backgroundservice.executeasync?view=dotnet-plat-ext-3.1#Microsoft_Extensions_Hosting_BackgroundService_ExecuteAsync_System_Threading_CancellationToken_) clearly states: `This method is called when the IHostedService starts. The implementation should return a task that represents the lifetime of the long running operation(s) being performed.` – Marc Selis Oct 20 '20 at 07:06
  • 1
    The HostBuilder doesn't offer that out of the box as it stands, please see https://github.com/dotnet/extensions/issues/1836 for the discussion on this. If you have multiple background services running then you need to manage this yourself. I admit it would be nice to have an extension or an event which is easy to plug into to get this behaviour. For code I have developed, I tend to ensure that all BackgroundServices have internal retries, if they fail then call a centralised service which can handle the shutdown. – nik0lai Oct 20 '20 at 12:15
  • 1
    Also another really good write up: https://stackoverflow.com/a/56871630/537674 – nik0lai Oct 20 '20 at 12:21
  • One downside is that this won't set an error exit code for the process. You can do that manually of course (`Environment.ExitCode`). Another option described here: https://stackoverflow.com/a/68460518/7613058 – Kyle McClellan Jul 20 '21 at 19:50
3

This has been changed from .NET 6 and onwards.

Change description

In previous .NET versions, when an exception is thrown from a BackgroundService.ExecuteAsync(CancellationToken) override, the host continues to run, and no message is logged.

Starting in .NET 6, when an exception is thrown from a BackgroundService.ExecuteAsync(CancellationToken) override, the exception is logged to the current ILogger. By default, the host is stopped when an unhandled exception is encountered.

https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/hosting-exception-handling

Mattias K
  • 45
  • 6