Изящно завершить общий хост в демоне .NET Core 2 linux

Я совершенно новичок как в .NET Core, так и в разработке демонов Linux. У меня было несколько похожих вопросов, таких как Изящное убийство Демон .NET Core, работающий в Linux или Изящное завершение работы с Generic Host в .NET Core 2.1, но они не решили мою проблему.

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

public static async Task Main(string[] args)
{
    try
    {
        Console.WriteLine("Starting");

        var host = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<DaemonService>();
            });

        System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 1");
        await host.RunConsoleAsync();
        System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 2");
    }
    finally
    {
        System.IO.File.WriteAllText("/path-to-app/_main-finally.txt", "Line 1");
    }
}

public class DaemonService : IHostedService, IDisposable
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        System.IO.File.WriteAllText("/path-to-app/_Start.txt", "Line 1");

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        System.IO.File.WriteAllText("/path-to-app/_Stop.txt", "Line 1");

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        System.IO.File.WriteAllText("/path-to-app/_Dispose.txt", "Line 1");
    }
}

Если я запускаю приложение из консоли, все работает как положено. Однако, когда он работает как демон, после выполнения kill <pid> или systemctl stop <service> выполняются методы StopAsync и Dispose, но больше ничего: ни in Main после блока await, ни finally.

Примечание. Я не использую ничего из ASP.NET Core. AFAIK это не нужно для того, что я делаю.

Я делаю что-то неправильно? Это ожидаемое поведение?


person Diego    schedule 23.04.2019    source источник
comment
Хм. Мне интересно, поскольку IHostedService используется в HostBuilder, это то, что управляет SIGTERM. После того, как Task помечен как завершенный, он определяет, что служба корректно завершила работу.   -  person Tubs    schedule 23.04.2019
comment
Это может быть все. Однако я нахожу это несколько запутанным, потому что у меня есть только один экземпляр службы, но у меня может быть несколько экземпляров из разных классов. Итак, если это так, как я должен корректно завершить работу из самой дальней области?   -  person Diego    schedule 23.04.2019
comment
Я бы предположил, что он будет продолжать спускаться по цепочке экземпляров, пока не завершит последний. Если вы переместите WriteAllText после RunConsoleAsync и finally внутрь StopAsync, это может сработать? Сохраняйте условие finally при перемещении   -  person Tubs    schedule 24.04.2019
comment
Если переместить блоки кода после await и блока finally в Dispose, это сработает. Но я думаю, что все начинает становиться действительно запутанным. Я считаю, что не стоит сообщать HostedService, как завершить всю программу.   -  person Diego    schedule 24.04.2019
comment
Я понимаю, откуда вы пришли, и у нас были те же опасения в прошлом. Однако, поскольку это работает как служба, мы пришли к выводу, что на самом деле имеет смысл содержать финализацию самой службы в этой области, аналогично тому, как работают приложения Asp.Net Core, предоставляя только службу внутри файла Program.cs и позволяя самой службе поддерживать свои зависимости. Мой совет состоял бы в том, чтобы содержать как можно больше в сервисе и просто инициализировать его с помощью метода Main.   -  person Tubs    schedule 24.04.2019
comment
Хорошо, кажется, это имеет смысл. Я мог бы пойти по этому пути. Я был удивлен тем, как трудно было найти информацию об этом. Спасибо @Tubs!   -  person Diego    schedule 24.04.2019
comment
Да, это совсем другой подход, который я полностью понял только с тех пор, как начал работать с микросервисами. Я резюмирую то, что мы обсуждали, как ответ, чтобы другие могли следовать в будущем.   -  person Tubs    schedule 24.04.2019
comment
Я также нашел этот ответ, может оказаться полезным для вас   -  person Tubs    schedule 24.04.2019


Ответы (2)


Подводя итоги беседы после первого вопроса.

Похоже, что IHostedService используется в HostBuilder, это то, что управляет SIGTERM. После того, как Task помечен как завершенный, он определяет, что служба корректно завершила работу. Это удалось исправить, переместив System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 2"); и код в блок finally внутри области действия службы. Модифицированный код представлен ниже.

public static async Task Main(string[] args)
{
    Console.WriteLine("Starting");

    var host = new HostBuilder()
        .ConfigureServices((hostContext, services) =>
        {
           services.AddHostedService<DaemonService>();
        });

    System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 1");
    await host.RunConsoleAsync();
}
public class DaemonService : IHostedService, IDisposable
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        System.IO.File.WriteAllText("/path-to-app/_Start.txt", "Line 1");

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
            return Task.CompletedTask;
    }

    public void Dispose()
    {
        try
        {
            System.IO.File.WriteAllText("/path-to-app/_Dispose.txt", "Line 1");
            System.IO.File.WriteAllText("/path-to-app/_Stop.txt", "Line 1");
        }
        finally
        {
            System.IO.File.WriteAllText("/path-to-app/_main-finally.txt", "Line 1");
        }
    }
}

Поскольку это работает как служба, мы пришли к выводу, что на самом деле имеет смысл содержать финализацию самой службы в этой области, аналогично тому, как работают приложения ASP.NET Core, предоставляя только службу внутри файла Program.cs и позволяя сам сервис для поддержания своих зависимостей.

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

person Tubs    schedule 24.04.2019

Этот ответ верен для dotnet core 3.1, но он должен быть таким же.

host.RunConsoleAsync() ожидает Sigterm или ctrl + C.

Переключитесь на host.Start(), и программа остановится, когда закончатся IHostedServices.

Я не думаю, что эта строка сейчас работает:

System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 2");
person Paul Totzke    schedule 11.03.2020