.NET Core Worker 服务作为 Windows 服务在调用 StopAsync 后不会停止

问题描述 投票:0回答:2

我有一个部署为 Windows 服务的辅助服务,我在停止 Windows 服务时遇到问题,它从日志中显示已停止,但当我登录到服务器时,它仍然显示正在运行。

public class Worker : BackgroundService
{
  private readonly ILogger<Worker> _logger;
  

  public Worker(ILogger<Worker> logger)
  {
    this._logger = logger;
  }

  public override Task StartAsync(CancellationToken cancellationToken)
  {
    _logger.LogInformation("Windows Service is Starting....   ");
    return base.StartAsync(cancellationToken);
  }
  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    //initial value coming as false
    _logger.LogInformation(string.Format("Cancellation token initial value : {0}", stoppingToken.IsCancellationRequested));

        while (!stoppingToken.IsCancellationRequested)
        {
           try
           {
              _logger.LogInformation("Starting the Process .....");
               
              //Dummy task used here, so it can cancel token.
              await Task.Delay(1000);       
         
              //Deliberately failing in the string.Format to test windows service stopping
              string.Format("Failing with exception here : {1}", "Test");
            }
           
            catch (Exception ex)
            {
              _logger.LogError(ex, ex.Message);
               var canclSrc = new CancellationTokenSource();
               canclSrc.Cancel();
               await StopAsync(canclSrc.Token);
                   
               //Cancellation token here is coming as true..
               _logger.LogInformation(string.Format("Cancellation token value : {0}", stoppingToken.IsCancellationRequested));
               _logger.LogInformation("Windows Service is stopped..");
             }
         }
    //Windows service failed and it is logging outside the while loop.
    _logger.LogInformation("Came out of while loop as windows service stopped!");
  }

 public override Task StopAsync(CancellationToken cancellationToken)
 {
   _logger.LogInformation("Stopping Windows Service...");
   return base.StopAsync(cancellationToken);
 }

它在日志中将取消标记设为 true,成功调用 StopAsync 方法。

当我登录到服务器并看到它显示正在运行的 Windows 服务并且 Windows 服务在某个地方被击中时,我没有看到任何日志任何服务只是挂起..

有关为什么即使调用 stopAsync 时我的 Windows 服务(这是一个辅助服务)也没有在服务器上停止的任何建议。

c# .net .net-core windows-services worker-service
2个回答
2
投票

等待 StopAsync(canclSrc.Token);

您的后台服务不应该自行停止。

如果你想拆除你的主机(即 Win32 服务),那么你应该 调用

IHostApplicationLifetime.StopApplication
(如我的博客所述)。像这样的东西:

public class Worker : BackgroundService
{
  private readonly ILogger<Worker> _logger;
  private readonly IHostApplicationLifetime _hostApplicationLifetime;

  public Worker(ILogger<Worker> logger, IHostApplicationLifetime hostApplicationLifetime) => (_logger, _hostApplicationLifetime) = (logger, hostApplicationLifetime);

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    _logger.LogInformation("Windows Service is Starting....   ");

    _logger.LogInformation(string.Format("Cancellation token initial value : {0}", stoppingToken.IsCancellationRequested));

    try
    {
      while (!stoppingToken.IsCancellationRequested)
      {
        _logger.LogInformation("Starting the Process .....");
               
        await Task.Delay(1000);       
         
        //Deliberately failing in the string.Format to test windows service stopping
        string.Format("Failing with exception here : {1}", "Test");
      }
    }
    catch (Exception ex)
    {
      _logger.LogError(ex, ex.Message);
    }
    finally
    {
      _logger.LogInformation("Windows Service is stopped..");
      _hostApplicationLifetime.StopApplication();
    }
  }
}

0
投票

我会坚持使用Microsoft的例子使用

Environment.Exit(1)

namespace App.WindowsService;

public sealed class WindowsBackgroundService : BackgroundService
{
    private readonly JokeService _jokeService;
    private readonly ILogger<WindowsBackgroundService> _logger;

    public WindowsBackgroundService(
        JokeService jokeService,
        ILogger<WindowsBackgroundService> logger) =>
        (_jokeService, _logger) = (jokeService, logger);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                string joke = _jokeService.GetJoke();
                _logger.LogWarning("{Joke}", joke);

                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
        catch (TaskCanceledException)
        {
            // When the stopping token is canceled, for example, a call made from services.msc,
            // we shouldn't exit with a non-zero exit code. In other words, this is expected...
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "{Message}", ex.Message);

            // Terminates this process and returns an exit code to the operating system.
            // This is required to avoid the 'BackgroundServiceExceptionBehavior', which
            // performs one of two scenarios:
            // 1. When set to "Ignore": will do nothing at all, errors cause zombie services.
            // 2. When set to "StopHost": will cleanly stop the host, and log errors.
            //
            // In order for the Windows Service Management system to leverage configured
            // recovery options, we need to terminate the process with a non-zero exit code.
            Environment.Exit(1);
        }
    }
}

如果您在应用程序中使用自己的取消标记来表示是时候重启了,那么:

try
{
    // Implementation
}
catch (TaskCanceledException ex)
{
    // perhaps log debug / trace
}
catch (Exception ex)
{
    // log error
}
finally
{
    // Note that `stoppingToken.IsCancellationRequested` being true means the service was
    // manually stopped using services.msc, so returning a non-zero exit code is not desired.
    if (!stoppingToken.IsCancellationRequested)
        Environment.Exit(1);
}

原因如下:

使用时

_hostApplicationLifetime.StopApplication();
Windows 服务管理器在配置为错误重启时不会重启服务,因为它没有获得非零退出代码。

来自我的测试:

Environment.Exit(1)
必须在
BackgroundService
ExecuteAsync
退出之前完成。

什么我的测试工作:

  • Main
    .
  • 返回非零整数
  • Environment.Exit(1) in
    Main
    after kestrel stops.
  • 取消传递给 kestrel 的
    RunAsync
    方法的取消标记。
  • Environment.ExitCode = 1
    返回之前设置
    ExecuteAsync

我还没有查看微软的 dotnet runtime source 来了解为什么会出现上述情况。

来自https://blog.stephencleary.com/2020/06/servicebase-gotcha-recovery-actions.html:

public class MyBackgroundService : BackgroundService
{
  private readonly IHostLifetime _hostLifetime;
  public MyBackgroundService(IHostLifetime hostLifetime) =>
      _hostLifetime = hostLifetime;

  protected override Task ExecuteAsync(CancellationToken stoppingToken)
  {
    try
    {
      // Implementation
    }
    catch (Exception)
    {
      if (_hostLifetime is ServiceBase serviceLifetime)
        serviceLifetime.ExitCode = -1;
      else
        Environment.ExitCode = -1;
    }
  }
}

Setting

serviceLifetime.ExitCode = -1
可能会起作用(我还没有测试过)。但是,正如我之前所说,
Environment.ExitCode = -1
不起作用。注意
serviceLifetime.ExitCode = -1
在不在 Windows 中时抛出。

此外,上面的问题是当 ExecuteAsync 退出时 kestrel 继续运行。

正如同一博客所指出的:

https://blog.stephencleary.com/2020/06/backgroundservice-gotcha-application-lifetime.html

public class MyBackgroundService : BackgroundService
{
    private readonly IHostApplicationLifetime _hostApplicationLifetime;
    public MyBackgroundService(IHostApplicationLifetime hostApplicationLifetime) =>
        _hostApplicationLifetime = hostApplicationLifetime;

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            // Implementation
        }
        finally
        {
            _hostApplicationLifetime.StopApplication();
        }
    }
}

这就是为什么我更喜欢

Environment.Exit(1)
至少在我不再使用 Windows 服务之前。

© www.soinside.com 2019 - 2024. All rights reserved.