IHost не возвращается при завершении задачи

#c# #.net-core-3.1 #background-service

#c# #.net-core-3.1 #фоновая служба

Вопрос:

Я пишу службу Windows (используя .NET Core 3.1), используя a BackgroundService , и, похоже, у меня проблема, когда я хочу программно остановить службу.

Моя основная функция заключается в следующем

 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>()

                    ));
                });
 

Фоновая служба выглядит следующим образом:

 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 amp;amp; !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 amp;amp; _workerService.FatalError)
                {
                    _workerService = null;
                    allDone = true;
                    Console.WriteLine("Service Quitting");
                }
            }

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

    

}
 

}

Итак, все работает так, как должно, и я запускаю это в отладчике или из командной строки (на самом деле я еще не установил его как службу Windows, поскольку я все еще пишу код). Функция Process выполняется правильно. Если он обнаруживает ошибку, которую невозможно восстановить, он устанавливает ее FatalError свойство, которое должно быть сигналом о том, что вся служба должна быть остановлена. Задача действительно завершена (правильные строки записываются в консоль), но строка Console.WriteLine("Ending"); никогда не выполняется. Похоже host.RunAsync(); , что никогда не возвращается (если я не нажму CTRL C).

Я не совсем уверен, что я делаю неправильно на данный момент. Кто-нибудь может указать мне направление записи?

Комментарии:

1. Основываясь на показанном коде, я не вижу ничего, что могло бы привести к остановке хоста.

2. Мне любопытно, как вы настроили свои службы. Зачем использовать делегат фабрики, когда все, что вы разрешили вручную, было бы введено автоматически, если была выполнена регистрация по умолчанию.?

Ответ №1:

Основываясь на показанном коде, я не вижу ничего, что могло бы привести к остановке хоста.

Размещенная служба после запуска не имеет никакого отношения к самому хосту приложения. Таким образом, даже когда ExecuteAsync она завершена, завершение этой функции не означает, что хост перестанет работать.

Вы могли бы рассмотреть возможность внедрения IHostApplicationLifetime в размещенную службу и явно указать приложению программную остановку;

Например

 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 amp;amp; !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 amp;amp; _workerService.FatalError) {
                    _workerService = null;
                    allDone = true;
                    Console.WriteLine("Service Quitting");
                    
                    lifetime.StopApplication(); //SIGNAL APPLICATION TO STOP
                }
            }    
        }
        Console.WriteLine($"Task Ending {stoppingToken.IsCancellationRequested}");
        return;
    }
}
 

Мне также интересно, как вы настроили свои службы. Зачем использовать делегат фабрики, когда все, что вы разрешили вручную, было бы введено автоматически, если была выполнена регистрация по умолчанию?

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

    services.AddSingleton<WorkerService>();

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

Ведение журнала и время жизни хоста добавляются для вас Host.CreateDefaultBuilder

Комментарии:

1. Спасибо, внедрение IHostApplicationLifetime было трюком, который сработал для меня.

2. С точки зрения того, почему служба настроена таким образом, простой ответ заключается в том, что я пытался разобраться в проблеме и попробовал несколько разных вещей. Я вернусь и пересмотрю это дальше.