我有一个部署为 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 服务(这是一个辅助服务)也没有在服务器上停止的任何建议。
等待 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();
}
}
}
我会坚持使用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
.Main
after kestrel stops.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 服务之前。