【问题标题】:.NET core BackgroundService does not shut down gracefully as daemon.NET 核心 BackgroundService 不会作为守护进程正常关闭
【发布时间】:2020-05-21 14:22:06
【问题描述】:

我正在开发 .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 stoppingcancellation requested!Worker stopped
请注意,应用程序会关闭。我们尝试关闭服务的方法如下:

  • 关闭服务器
  • systemctl stop my-worker.service
  • kill
  • kill -SIGTERM
  • kill -SIGINT

什么有效

如果我像这样启动工作程序: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()
我用UseWindowsService() 替换了UseSystemd(),并将它作为Windows 服务安装在Windows 机器上。然后继续通过“服务”面板启动和停止服务,并按预期看到关闭日志记录。
所以,我很想假设在实现中没有问题,而是在设置中的某个地方。

【问题讨论】:

  • 你在 Docker 中运行吗?
  • @StephenCleary 实际上没有,它在 AWS Debian EC2 上运行
  • 我可能是错的,但我在 github 上发现了问题——也许你会发现一些对你有用的东西。如果是 - 然后在成功的情况下发布答案。 github.com/dotnet/runtime/issues/35990
  • 你为什么要使用Task?您的 DoWork 看起来不像是异步的。只需在ExecuteAsync 中的令牌上永远循环并返回Task.CompletedTask
  • @Andy 老实说,我现在不记得所有细节了,这个问题很久以前就解决了,我错过了在这里更新问题。我想你有一个观点,但我不确定这会解决问题。

标签: .net-core systemd background-service


【解决方案1】:

问题似乎在于 NLog 的关闭。通过执行以下操作已解决此问题:LogManager.AutoShutdown = false; 并在 Worker::StopAsync 添加 LogManager.Shutdown();

【讨论】:

    【解决方案2】:

    我也遇到了同样的问题,似乎是因为systemctl stop没有发送SIGTERM信号,所以为了让服务按预期工作,我用以下参数配置了服务文件:

    [Service]
    Type=simple
    ExecStop=/bin/kill -s TERM $ MAINPID
    

    现在,当我运行 systemctl stop MyService 时会调用 StopAsync 和 Dispose

    【讨论】:

      【解决方案3】:

      这听起来像是一个线程问题......应用程序在任务完成之前退出。

      你可以试试这个:

              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 的任何调用后退出。

      【讨论】:

      • 这个假设是否支持它在设置为 WindowsService 或控制台时可以正常工作的事实?反正我会试一试的
      【解决方案4】:

      以下服务定义确实解决了我的问题。我粘贴了整个服务定义,所以不要混淆。

      这两个属性使 ASP.NET Core 服务正常关闭:

      • 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

      【讨论】:

        猜你喜欢
        • 2021-11-16
        • 1970-01-01
        • 2023-03-02
        • 1970-01-01
        • 2018-04-30
        • 2011-04-25
        • 2014-09-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多