.NET 核心 BackgroundService 没有作为守护进程正常关闭
.NET core BackgroundService does not shut down gracefully as daemon
我正在开发 .NET Core 3.1 后台服务,将作为守护进程安装在 Debian AWS EC2 实例上。
重要的是优雅地关闭守护进程以停止 运行 任务并完成一些要处理的任务(发送一些事件等)。
基本实现如下所示:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace MyApp.WorkerService
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).UseSystemd().Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
}
你可以看到我在这里使用SystemdLifetime
。
工人如下:
using System;
using System.Threading;
using System.Threading.Tasks;
using AdClassifier.App.Audit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NLog;
namespace MyApp.WorkerService
{
public class Worker : BackgroundService
{
private static readonly ILogger Logger = LogManager.GetLogger(typeof(Worker).FullName);
private readonly int _jobPollIntervalMilliseconds;
public IServiceProvider Services { get; }
public Worker(IServiceProvider services, IConfiguration configuration)
{
Services = services;
_jobPollIntervalMilliseconds = configuration.GetValue<int>("JobPollIntervalMilliseconds");
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Logger.Info("Worker running.");
var task = new Task(o => DoWork(stoppingToken), stoppingToken);
task.Start();
return task;
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
Logger.Info("Worker stopping");
await base.StopAsync(cancellationToken);
Logger.Info("Worker stopped");
}
private void DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = Services.CreateScope())
{
// do some work
}
Thread.Sleep(_jobPollIntervalMilliseconds);
}
Logger.Info("cancellation requested!");
}
}
}
问题
正如我提到的,我们将其设置为守护进程,如下所示
[Unit]
Description=my worker
Requires=deploy-my-worker.service
After=multi-user.target deploy-my-worker.service
ConditionFileIsExecutable=/home/my-worker/current/myworker
[Service]
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=true
Environment=ASPNETCORE_URLS=http://*:5000
Environment=DOTNET_ENVIRONMENT=Staging
Environment=ASPNETCORE_ENVIRONMENT=Staging
WorkingDirectory=/home/my-worker/current
ExecStart=/home/my-worker/current/myworker
SyslogIdentifier=my-worker
Restart=always
RestartSec=10
KillSignal=SIGTERM
User=usr
Group=usrgroup
[Install]
WantedBy=multi-user.target
问题是 worker 不会正常停止。我正在检查以下日志条目的日志,但它们没有出现:
Worker stopping
、cancellation requested!
、Worker stopped
请注意,应用程序 确实关闭了 。我们为关闭服务所做的尝试如下:
- 关闭服务器
systemctl stop my-worker.service
kill
kill -SIGTERM
kill -SIGINT
有效方法
如果我这样启动 worker:usr@ip-10-168-19-126:~/my-worker/current$ ./myworker
然后按 Ctrl-C
(应该是 SIGINT
),应用程序停止,在我的日志中我可以看到正确消息:
2020-05-21 16:16:57.9676|INFO|MyApp.WorkerService.Worker|Worker stopping
2020-05-21 16:16:57.9937|INFO|MyApp.WorkerService.Worker|cancellation requested!
2020-05-21 16:16:57.9937|INFO|MyApp.WorkerService.Worker|Worker stopped
2020-05-21 16:16:57.9980 Info AppDomain Shutting down. Logger closing...
关于如何使守护程序按预期工作的任何想法?
注意:
我有充分的理由相信问题出在守护程序设置的某个地方,或者 UseSystemd()
.
我将 UseSystemd()
替换为 UseWindowsService()
并将其作为 Windows 服务安装在 windows 机器上。然后继续通过“服务”面板启动和停止服务,并按预期看到关闭日志记录。
所以,我很想假设在实现中没有问题,而是在设置中的某个地方。
这听起来像是一个线程问题...应用程序在任务完成之前退出。
你可以试试这个:
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
System.Threading.Thread.CurrentThread.IsBackground = false;
Logger.Info("Worker running.");
var task = Task.Run(() => DoWork(stoppingToken), stoppingToken);
task.Wait();
return task;
}
关键是让正在执行任务的线程等待任务,而不是在后台。我相信应用程序将在完成对 ExecuteAsync 的任何调用后退出。
我遇到了同样的问题,似乎是因为 systemctl stop
没有发送 SIGTERM 信号,所以为了使服务按预期工作,我使用以下参数配置了服务文件:
[Service]
Type=simple
ExecStop=/bin/kill -s TERM $ MAINPID
现在,当我 运行 systemctl stop MyService
时调用 StopAsync 和 Dispose
看来问题出在NLog的关闭上。这是通过执行以下操作修复的:LogManager.AutoShutdown = false;
并在 Worker::StopAsync
中添加 LogManager.Shutdown();
以下服务定义确实解决了我的问题。我粘贴了整个服务定义,所以不要混淆。
这两个属性使 ASP.NET 核心服务正常关闭:
- KillSignal=SIGTERM
- ExecStop=/bin/kill ${MAINPID}
如果它也能解决您的问题,请告诉我。
[Unit]
Description=WeatherDisplay.Api Service
After=network-online.target firewalld.service
Wants=network-online.target
[Service]
Type=notify
WorkingDirectory=/home/pi/WeatherDisplay.Api
ExecStart=/home/pi/WeatherDisplay.Api/WeatherDisplay.Api
ExecStop=/bin/kill ${MAINPID}
KillSignal=SIGTERM
SyslogIdentifier=WeatherDisplay.Api
User=pi
Group=pi
Restart=always
RestartSec=5
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=DOTNET_ROOT=/home/pi/dotnet
[Install]
WantedBy=multi-user.target
您可以在这里找到完整的项目:
https://github.com/thomasgalliker/PiWeatherStation
我正在开发 .NET Core 3.1 后台服务,将作为守护进程安装在 Debian AWS EC2 实例上。
重要的是优雅地关闭守护进程以停止 运行 任务并完成一些要处理的任务(发送一些事件等)。
基本实现如下所示:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace MyApp.WorkerService
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).UseSystemd().Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
}
你可以看到我在这里使用SystemdLifetime
。
工人如下:
using System;
using System.Threading;
using System.Threading.Tasks;
using AdClassifier.App.Audit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NLog;
namespace MyApp.WorkerService
{
public class Worker : BackgroundService
{
private static readonly ILogger Logger = LogManager.GetLogger(typeof(Worker).FullName);
private readonly int _jobPollIntervalMilliseconds;
public IServiceProvider Services { get; }
public Worker(IServiceProvider services, IConfiguration configuration)
{
Services = services;
_jobPollIntervalMilliseconds = configuration.GetValue<int>("JobPollIntervalMilliseconds");
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Logger.Info("Worker running.");
var task = new Task(o => DoWork(stoppingToken), stoppingToken);
task.Start();
return task;
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
Logger.Info("Worker stopping");
await base.StopAsync(cancellationToken);
Logger.Info("Worker stopped");
}
private void DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = Services.CreateScope())
{
// do some work
}
Thread.Sleep(_jobPollIntervalMilliseconds);
}
Logger.Info("cancellation requested!");
}
}
}
问题
正如我提到的,我们将其设置为守护进程,如下所示
[Unit]
Description=my worker
Requires=deploy-my-worker.service
After=multi-user.target deploy-my-worker.service
ConditionFileIsExecutable=/home/my-worker/current/myworker
[Service]
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=true
Environment=ASPNETCORE_URLS=http://*:5000
Environment=DOTNET_ENVIRONMENT=Staging
Environment=ASPNETCORE_ENVIRONMENT=Staging
WorkingDirectory=/home/my-worker/current
ExecStart=/home/my-worker/current/myworker
SyslogIdentifier=my-worker
Restart=always
RestartSec=10
KillSignal=SIGTERM
User=usr
Group=usrgroup
[Install]
WantedBy=multi-user.target
问题是 worker 不会正常停止。我正在检查以下日志条目的日志,但它们没有出现:
Worker stopping
、cancellation requested!
、Worker stopped
请注意,应用程序 确实关闭了 。我们为关闭服务所做的尝试如下:
- 关闭服务器
systemctl stop my-worker.service
kill
kill -SIGTERM
kill -SIGINT
有效方法
如果我这样启动 worker:usr@ip-10-168-19-126:~/my-worker/current$ ./myworker
然后按 Ctrl-C
(应该是 SIGINT
),应用程序停止,在我的日志中我可以看到正确消息:
2020-05-21 16:16:57.9676|INFO|MyApp.WorkerService.Worker|Worker stopping
2020-05-21 16:16:57.9937|INFO|MyApp.WorkerService.Worker|cancellation requested!
2020-05-21 16:16:57.9937|INFO|MyApp.WorkerService.Worker|Worker stopped
2020-05-21 16:16:57.9980 Info AppDomain Shutting down. Logger closing...
关于如何使守护程序按预期工作的任何想法?
注意:
我有充分的理由相信问题出在守护程序设置的某个地方,或者 UseSystemd()
.
我将 UseSystemd()
替换为 UseWindowsService()
并将其作为 Windows 服务安装在 windows 机器上。然后继续通过“服务”面板启动和停止服务,并按预期看到关闭日志记录。
所以,我很想假设在实现中没有问题,而是在设置中的某个地方。
这听起来像是一个线程问题...应用程序在任务完成之前退出。
你可以试试这个:
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
System.Threading.Thread.CurrentThread.IsBackground = false;
Logger.Info("Worker running.");
var task = Task.Run(() => DoWork(stoppingToken), stoppingToken);
task.Wait();
return task;
}
关键是让正在执行任务的线程等待任务,而不是在后台。我相信应用程序将在完成对 ExecuteAsync 的任何调用后退出。
我遇到了同样的问题,似乎是因为 systemctl stop
没有发送 SIGTERM 信号,所以为了使服务按预期工作,我使用以下参数配置了服务文件:
[Service]
Type=simple
ExecStop=/bin/kill -s TERM $ MAINPID
现在,当我 运行 systemctl stop MyService
看来问题出在NLog的关闭上。这是通过执行以下操作修复的:LogManager.AutoShutdown = false;
并在 Worker::StopAsync
中添加 LogManager.Shutdown();
以下服务定义确实解决了我的问题。我粘贴了整个服务定义,所以不要混淆。
这两个属性使 ASP.NET 核心服务正常关闭:
- KillSignal=SIGTERM
- ExecStop=/bin/kill ${MAINPID}
如果它也能解决您的问题,请告诉我。
[Unit]
Description=WeatherDisplay.Api Service
After=network-online.target firewalld.service
Wants=network-online.target
[Service]
Type=notify
WorkingDirectory=/home/pi/WeatherDisplay.Api
ExecStart=/home/pi/WeatherDisplay.Api/WeatherDisplay.Api
ExecStop=/bin/kill ${MAINPID}
KillSignal=SIGTERM
SyslogIdentifier=WeatherDisplay.Api
User=pi
Group=pi
Restart=always
RestartSec=5
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=DOTNET_ROOT=/home/pi/dotnet
[Install]
WantedBy=multi-user.target
您可以在这里找到完整的项目: https://github.com/thomasgalliker/PiWeatherStation