在 .NET Core 2 linux 守护进程中优雅地关闭通用主机
Gracefully shutdown a generic host in .NET Core 2 linux daemon
我对 .NET Core 和开发 linux 守护进程都是全新的。我遇到过几个类似的问题,例如 or Graceful 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>
之后,会执行 StopAsync
和 Dispose
方法,但不会执行其他任何事情:不是在 Main
之后 await
和 finally
块之后。
注意:我没有使用 ASP.NET Core 中的任何内容。据我所知,我正在做的事情没有必要。
我做错了什么吗?这是预期的行为吗?
在初始问题下方总结对话。
它似乎在 HostBuilder
中使用了 IHostedService
,它控制着 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 核心应用程序功能的方式通过仅在 Program.cs
文件中提供服务并允许服务本身维护其依赖关系。
我的建议是在服务中包含尽可能多的内容,并使用 Main
方法初始化它。
这个答案对于 dotnet core 3.1 是正确的,但应该是一样的。
host.RunConsoleAsync() 等待 Sigterm 或 ctrl + C.
切换到 host.Start() 并且程序在 IHostedServices 完成时停止。
我认为当前没有命中此行:
System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 2");
我对 .NET Core 和开发 linux 守护进程都是全新的。我遇到过几个类似的问题,例如
我使用托管服务构建了一个非常简单的控制台应用程序作为测试。我希望它 运行 作为守护进程,但我在正确关闭它时遇到问题。当它在 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>
之后,会执行 StopAsync
和 Dispose
方法,但不会执行其他任何事情:不是在 Main
之后 await
和 finally
块之后。
注意:我没有使用 ASP.NET Core 中的任何内容。据我所知,我正在做的事情没有必要。
我做错了什么吗?这是预期的行为吗?
在初始问题下方总结对话。
它似乎在 HostBuilder
中使用了 IHostedService
,它控制着 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 核心应用程序功能的方式通过仅在 Program.cs
文件中提供服务并允许服务本身维护其依赖关系。
我的建议是在服务中包含尽可能多的内容,并使用 Main
方法初始化它。
这个答案对于 dotnet core 3.1 是正确的,但应该是一样的。
host.RunConsoleAsync() 等待 Sigterm 或 ctrl + C.
切换到 host.Start() 并且程序在 IHostedServices 完成时停止。
我认为当前没有命中此行:
System.IO.File.WriteAllText("/path-to-app/_main.txt", "Line 2");