优雅地关闭.NET Core 2 linux守护程序中的通用主机

时间:2019-04-23 13:31:58

标签: c# linux .net-core daemon start-stop-daemon

我对.NET Core和正在开发的Linux守护程序都是全新的。我遇到过一些类似的问题,例如Killing gracefully a .NET Core daemon running on LinuxGraceful shutdown with Generic Host in .NET Core 2.1,但它们并不能解决我的问题。

我使用托管服务构建了一个非常简单的控制台应用程序作为测试。我希望它作为守护程序运行,但是我在正确关闭它方面遇到问题。从Windows和Linux的控制台运行时,一切正常。

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>之后,将执行StopAsyncDispose方法,但没有执行其他操作:{{1中的}} Mainawait块之后。

注意:我没有使用ASP.NET Core中的任何内容。 AFAIK不需要我做什么。

我做错什么了吗?这是预期的行为吗?

2 个答案:

答案 0 :(得分:0)

在初始问题下方总结对话。

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应用程序通过仅在{内提供服务来运行的方式。 {1}}文件,并允许服务本身维护其依赖性。

我的建议是在服务中包含尽可能多的内容,而只使用Program.cs方法对其进行初始化。

答案 1 :(得分:0)

此答案对于dotnet core 3.1是正确的,但应该相同。

host.RunConsoleAsync()等待Sigterm或Ctrl + C。

切换到host.Start(),然后在IHostedServices完成时程序停止。

我不认为此行当前被点击:

System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 2");