簡體   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