Home > Back-end >  IHost not returning when Task completed
IHost not returning when Task completed

Time:12-11

I'm writing a Windows Service (using .NET Core 3.1), using a BackgroundService and I seem to have an issue when I want to programmatically stop the service.

My main function is as follows

static async Task Main(string[] args)
{
    IHost host = null;
    try
    {
        host = CreateService(args).Build();
        await host.RunAsync();
        Console.WriteLine("Ending");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception");
    }
    finally
    {
        if (host is IAsyncDisposable d) await d.DisposeAsync();
    }
}

    public static IHostBuilder CreateService(string[] args) =>

         Host.CreateDefaultBuilder(args)
                .UseWindowsService(options =>
                {
                    options.ServiceName = "My Service";

                })
                .ConfigureServices((hostContext, services) =>
                {
                    IConfiguration configuration = hostContext.Configuration;
                    ServiceOptions options = configuration.GetSection("ServiceOptions").Get<ServiceOptions>();
                    WorkerService sService = new WorkerService();
                    services.AddSingleton(options);
                    services.AddSingleton(sService);

                    services.AddHostedService<WindowsBackgroundService>(service => new WindowsBackgroundService(
                        service.GetService<WorkerService>(),
                        service.GetService<ILogger<WindowsBackgroundService>>(),
                        service.GetService<ServerOptions>()

                    ));
                });

The background service is as follows:

public sealed class WindowsBackgroundService : BackgroundService
{
    private WorkerService _workerService;
    private ServerOptions _options;
    private ILogger<WindowsBackgroundService> _logger;

    public WindowsBackgroundService(
        WorkerService workerService, ILogger<WindowsBackgroundService> logger,
        ServiceOptions serviceOptions) => (_workerService, _logger, _options) = (workerService, logger, serviceOptions);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        bool allDone = false;
        while (!stoppingToken.IsCancellationRequested && !allDone)
        {
            await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
            //  Log to watchdog 

            //  Check if we are in the run window
            if (_options.InActivePeriod())
            {

        
                Console.WriteLine("Process Queue");
               
                var processResult = _workerService.Process(_options);
                if (!processResult && _workerService.FatalError)
                {
                    _workerService = null;
                    allDone = true;
                    Console.WriteLine("Service Quitting");
                }
            }

        }
        Console.WriteLine($"Task Ending {stoppingToken.IsCancellationRequested}");
        return;
    }

    

}

}

So everything runs as it should and I run this under the debugger or from the command line (I haven't actually installed it as a Windows Service yet as I'm still writing the code). The function Process executes correctly. If it encounters an error that cannot be recovered, it sets it's FatalError property that is supposed to be a signal that the whole service should be stopped. The Task does indeed complete (the correct lines are written to console) but the line Console.WriteLine("Ending"); is never executed. It looks like the host.RunAsync(); never returns (unless I do a CTRL C).

I'm not entirely certain what I am doing wrong at this point. Can anyone point me in the write direction?

CodePudding user response:

Based on the shown code I see nothing that would cause the host to stop.

The hosted service, once started has no bearing on the application host itself. So even when ExecuteAsync is completed, the completion of that function does not mean that the host will stop running.

You could consider injecting IHostApplicationLifetime into the hosted service and explicitly telling the application to stop programmatically;

For example

public sealed class WindowsBackgroundService : BackgroundService {
    private WorkerService _workerService;
    private ServerOptions _options;
    private ILogger<WindowsBackgroundService> _logger;
    private IHostApplicationLifetime lifetime;

    public WindowsBackgroundService(
        WorkerService workerService, ILogger<WindowsBackgroundService> logger,
        ServiceOptions serviceOptions, IHostApplicationLifetime lifetime) 
            => (_workerService, _logger, _options, this.lifetime) = (workerService, logger, serviceOptions, lifetime);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
        bool allDone = false;
        while (!stoppingToken.IsCancellationRequested && !allDone) {
            await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
            //  Log to watchdog 

            //  Check if we are in the run window
            if (_options.InActivePeriod()) {
                Console.WriteLine("Process Queue");
               
                var processResult = _workerService.Process(_options);
                if (!processResult && _workerService.FatalError) {
                    _workerService = null;
                    allDone = true;
                    Console.WriteLine("Service Quitting");
                    
                    lifetime.StopApplication(); //SIGNAL APPLICATION TO STOP
                }
            }    
        }
        Console.WriteLine($"Task Ending {stoppingToken.IsCancellationRequested}");
        return;
    }
}

I am also curious about how you configured your services. Why use the factory delegate when everything you resolved manually would have been injected automatically if the default registration was done?

//...
.ConfigureServices((hostContext, services) => {
    IConfiguration configuration = hostContext.Configuration;
    ServiceOptions options = configuration.GetSection("ServiceOptions").Get<ServiceOptions>();
    services.AddSingleton(options);

    services.AddSingleton<WorkerService>();

    services.AddHostedService<WindowsBackgroundService>();
});

The logging and and host lifetime are added for you by the Host.CreateDefaultBuilder

  • Related