繁体   English   中英

.NET 6 BackgroundServices 中的错误处理托管在 Windows 服务中

[英]Error handling in .NET 6 BackgroundServices hosted in a Windows Service

我一直在阅读有关部署 Windows 服务以运行 Worker App 的 MS 文档

MS 代码示例谈到需要在异常处理程序中添加Environment.Exit(1) ,以便 Windows 服务管理可以利用配置的恢复选项。

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 (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);
    }
}

有几个概念我不清楚,希望有人能提供建议:

在我自己的项目中,我的后台服务包括各种类和操作,例如 Azure IOT Hubs 设备客户端的连接管理。 在某些情况下,我根本不想强制环境,即整个应用程序在每个捕获/异常情况下退出,但文档不清楚我们是否应该这样做? 我的意思是,如果我们每次都要简单地清除应用程序的运行,为什么要捕获异常? 对我没有意义...

下一点参考以下语句“要正确允许重新启动服务,您可以使用非零退出代码调用 Environment.Exit”,但在本文前面,它还讨论了可用于“BackgroundServiceExceptionBehavior”的两个选项:

  • 忽略 - 忽略 BackgroundService 中引发的异常。 停止主机
  • 当抛出未处理的异常时,IHost 将停止。

在我看来,一个未处理的异常意味着该应用程序已经发现了一些没有在正确的地方适当地捕获的东西,即不存在 try/catch 块的地方。 那么如何为他们尚未考虑的事情提供“Environment.Exit(1)”呢? 在这种情况下会发生什么?

这篇文章读给我听的方式表明,我们可以确保 Windows 服务成功管理应用程序的重新启动的唯一方法是从我们故意捕获的任何异常中,但同样与一般文章的内容无关暗示会发生。

完全糊涂了:(

如 .NET 6 之前的文章中所述,后台服务中未处理的异常不会以任何方式影响应用程序 - 例如,如果您有一个应用程序,它的唯一工作是处理后台服务中的某个队列并且该服务将失败,那么该应用程序将继续就像什么都没发生一样运行,这在这种情况下显然是错误的。 这就是它在 .NET 6 中修复的原因。

作为服务恢复选项和 .NET BackgroundService实例段落指出未处理异常的新默认行为是StopHost ,其行为就像应用程序(Windows 服务)将正常退出(退出代码为 0)一样:

但是它干净利落地停止了,也就是说Windows服务管理系统不会重启服务

如果您希望您的应用程序由 Windows 服务管理系统(如果已设置为这样做)自动重新启动,您需要“处理”异常并以指示失败的非零退出代码结束应用程序。

显然,如果一些具体的异常是可恢复的,你只需要恢复:

while (!stoppingToken.IsCancellationRequested)
{
    try
    {
        // some job
    }
    catch (SomeRecoverableException e)
    {
        // handle it
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "{Message}", ex.Message);
        Environment.Exit(1);
    }
}

Ignore选项的存在是为了向后兼容,因此可以恢复到以前的行为(我无法想到为什么可能需要它但仍然需要它)。

tl;博士

  • 从 .NET Core 2 到 .NET 5(或者更确切地说,.NET 平台扩展 2 到 5),引发异常的 BackgroundService 使您的主机应用程序保持运行,但如果有任何其他任务,但没有该特定后台服务运行) .
  • 从 .NET(平台扩展)6 开始,引发异常的 BackgroundService 将关闭主机应用程序,退出代码为 0。
  • 退出代码 0 不会触发服务控制管理器的失败选项,即 Windows 不会知道您的服务崩溃,也不会重新启动它。
  • Ergo:您必须自己告诉 SCM 服务已崩溃,并且使用非零退出代码退出是一种方法。

.NET BackgroundService 是一个与平台无关的构造,通过IHostedService机制( services.AddHostedService() )表示 .NET 应用程序中的长期运行任务。

您可以在 ASP.NET 应用程序以及 WinForms 应用程序或普通的旧控制台应用程序中拥有托管服务。

这些托管的后台服务与特定于 Windows 的事物(即 Windows 服务)没有任何联系。

您可以拥有一个完全不执行任何操作的 Windows 服务,或者运行您的自定义任务的服务,或者运行一个或多个 BackgroundServices(或其他 IHostedServices)的服务,或者后者的组合。 您甚至可以让一个应用程序托管多个 Windows 服务,每个服务运行零个或多个 IHostedServices,但让我们忽略这一点。


现在,当您的服务从零托管服务开始时,您希望它做什么? 它可能应该继续做它应该做的任何其他事情。 如果 Windows 服务运行多个 BackgroundServices 并且一站式服务怎么办? 应该会继续吧。 如果抛出异常怎么办?

可能不太好,如果您的进程除了托管那个现在已经崩溃的服务之外还做其他事情,您可能不希望这些工作继续进行。 因此,由于 .NET 平台扩展 6 BackgroundService 生命周期发生了变化,运行时从整个应用程序中拉出地毯,因此您的 Windows 服务所做的一切都停止发生。

但这并不完全奏效。 是的,它会终止您的应用程序并将一两个事件 (9, 10) 记录到应用程序日志中,但它不会设置退出代码,也不会与服务控制管理器 (SCM) 沟通服务失败

它在该代码块的注释中说得对,但不够清楚:这是一个问题。 当您不设置退出代码并且不向 SCM 报告错误时,SCM 将不会运行服务的恢复操作。 因此,您的服务将保持关闭,直到您重新启动机器(假设服务自动启动)或您手动启动服务。

设置进程退出代码并从异常处理程序中退出应用程序是让服务控制管理器知道您的服务崩溃的一种方法,这就是文档中的代码块所做的:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    try
    {
        // Do your long-running work
    }
    catch (Exception ex)
    {
        // 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);
    }
}

但是,您必须在所有 BackgroundServices 中这样做,并且 .NET 平台扩展(包含这些 Windows 服务帮助程序类)涉及错误处理,因此我编写了一个包装库来解决这个问题: CodeCaster .WindowsServiceExtensions

使用我的库,您不必为这个 Windows 服务特定的错误处理而烦恼(它已为您完成):

public class MyCoolBackgroundService : WindowsServiceBackgroundService
{
    public MyCoolBackgroundService(
        ILogger<MyCoolBackgroundService> logger,
        IHostLifetime hostLifetime
    )
        : base(logger, hostLifetime)
    {
    }

    protected override async Task TryExecuteAsync(CancellationToken stoppingToken)
    {
        // Do your continuous or periodic background work.
        await SomeLongRunningTaskAsync();

        // This will report to the SCM that your service failed.
        throw new Exception("Foo");
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM