.NET Worker Service 如何优雅退出
上一篇文章中我們了解了?.NET Worker Service 的入門知識[1],今天我們接著介紹一下如何優雅地關閉和退出 Worker Service。
Worker 類
從上一篇文章中,我們已經知道了 Worker Service 模板為我們提供三個開箱即用的核心文件,其中?Worker?類是繼承自抽象基類?BackgroundService?的,而?BackgroundService?實現了?IHostedService?接口。最終?Worker?類會被注冊為托管服務,我們處理任務的核心代碼就是寫在?Worker?類中的。所以,我們需要重點了解一下?Worker?及其基類。
先來看看它的基類?BackgroundService?:
基類?BackgroundService?中有三個可重寫的方法,可以讓我們綁定到應用程序的生命周期中:
抽象方法?ExecuteAsync:作為應用程序主要入口點的方法。如果此方法退出,則應用程序將關閉。我們必須在?Worker?中實現它。
虛方法?StartAsync:在應用程序啟動時調用。如果需要,可以重寫此方法,它可用于在服務啟動時一次性地設置資源;當然,也可以忽略它。
虛方法?StopAsync:在應用程序關閉時調用。如果需要,可以重寫此方法,在關閉時釋放資源和銷毀對象;當然,也可以忽略它。
默認情況下?Worker?只重寫必要的抽象方法?ExecuteAsync。
新建一個 Worker Service 項目
我們來新建一個 Worker Service,使用?Task.Delay?來模擬關閉前必須完成的一些操作,看看是否可以通過簡單地在?ExecuteAsync?中?Delay?來模擬實現優雅關閉。
需要用到的開發工具:
Visual Studio Code:https://code.visualstudio.com/
最新的 .NET SDK:https://dotnet.microsoft.com/download
安裝好以上工具后,在終端中運行以下命令,創建一個 Worker Service 項目:
dotnet new Worker -n "MyService"創建好 Worker Service 后,在 Visual Studio Code 中打開應用程序,然后構建并運行一下,以確保一切正常:
dotnet build dotnet run按?CTRL+C?鍵關閉服務,服務會立即退出,默認情況下 Worker Service 的關閉就是這么直接!在很多場景(比如內存中的隊列)中,這不是我們想要的結果,有時我們不得不在服務關閉前完成一些必要的資源回收或事務處理。
我們看一下?Worker?類的代碼,會看到它只重寫了基類?BackgroundService?中的抽象方法?ExecuteAsync:
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {while (!stoppingToken.IsCancellationRequested){_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);await Task.Delay(1000, stoppingToken);} }我們嘗試修改一下此方法,退出前做一些業務處理:
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {while (!stoppingToken.IsCancellationRequested){_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);// await Task.Delay(1000, stoppingToken);await Task.Delay(1000);}_logger.LogInformation("等待退出 {time}", DateTimeOffset.Now);Task.Delay(60_000).Wait(); //模擬退出前需要完成的工作_logger.LogInformation("退出 {time}", DateTimeOffset.Now); }然后測試一下,看它是不是會像我們預期的那樣先等待 60 秒再關閉。
dotnet build dotnet run按?CTRL+C?鍵關閉服務,我們會發現,它在輸出 “等待退出” 后,并沒有等待 60 秒并輸出 “退出” 之后再關閉,而是很快便退出了。這就像我們熟悉的控制臺應用程序,默認情況下,在我們點了右上角的關閉按鈕或者按下?CTRL+C?鍵時,會直接關閉一樣。
Worker Service 優雅退出
那么,怎么才能實現優雅退出呢?
方法其實很簡單,那就是將?IHostApplicationLifetime?注入到我們的服務中,然后在應用程序停止時手動調用?IHostApplicationLifetime?的?StopApplication?方法來關閉應用程序。
修改?Worker?的構造函數,注入?IHostApplicationLifetime:
private readonly IHostApplicationLifetime _hostApplicationLifetime; private readonly ILogger<Worker> _logger;public Worker(IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger) {_hostApplicationLifetime = hostApplicationLifetime;_logger = logger; }然后在?ExecuteAsync?中,處理完退出前必須完成的業務邏輯后,手動調用?IHostApplicationLifetime?的?StopApplication?方法,下面是豐富過的?ExecuteAsync?代碼:
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {try{// 這里實現實際的業務邏輯while (!stoppingToken.IsCancellationRequested){try{_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);await SomeMethodThatDoesTheWork(stoppingToken);}catch (Exception ex){_logger.LogError(ex, "Global exception occurred. Will resume in a moment.");}await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);}}finally{_logger.LogWarning("Exiting application...");GetOffWork(stoppingToken); //關閉前需要完成的工作_hostApplicationLifetime.StopApplication(); //手動調用 StopApplication} }private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken) {_logger.LogInformation("我愛工作,埋頭苦干ing……");await Task.CompletedTask; }/// <summary> /// 關閉前需要完成的工作 /// </summary> private void GetOffWork(CancellationToken cancellationToken) {_logger.LogInformation("啊,糟糕,有一個緊急 bug 需要下班前完成!!!");_logger.LogInformation("啊啊啊,我愛加班,我要再干 20 秒,Wait 1 ");Task.Delay(TimeSpan.FromSeconds(20)).Wait();_logger.LogInformation("啊啊啊啊啊啊,我愛加班,我要再干 1 分鐘,Wait 2 ");Task.Delay(TimeSpan.FromMinutes(1)).Wait();_logger.LogInformation("啊哈哈哈哈哈,終于好了,下班走人!"); }此時,再次?dotnet run?運行服務,然后按?CTRL+C?鍵關閉服務,您會發現關閉前需要完成的工作?GetOffWork?運行完成后才會退出服務了。
至此,我們已經實現了 Worker Service 的優雅退出。
StartAsync 和 StopAsync
為了更進一步了解 Worker Service,我們再來豐富一下我們的代碼,重寫基類?BackgroundService?的?StartAsync?和?StopAsync?方法:
public class Worker : BackgroundService {private bool _isStopping = false; //是否正在停止工作private readonly IHostApplicationLifetime _hostApplicationLifetime;private readonly ILogger<Worker> _logger;public Worker(IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger){_hostApplicationLifetime = hostApplicationLifetime;_logger = logger;}public override Task StartAsync(CancellationToken cancellationToken){_logger.LogInformation("上班了,又是精神抖擻的一天,output from StartAsync");return base.StartAsync(cancellationToken);}protected override async Task ExecuteAsync(CancellationToken stoppingToken){try{// 這里實現實際的業務邏輯while (!stoppingToken.IsCancellationRequested){try{_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);await SomeMethodThatDoesTheWork(stoppingToken);}catch (Exception ex){_logger.LogError(ex, "Global exception occurred. Will resume in a moment.");}await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);}}finally{_logger.LogWarning("Exiting application...");GetOffWork(stoppingToken); //關閉前需要完成的工作_hostApplicationLifetime.StopApplication(); //手動調用 StopApplication}}private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken){if (_isStopping)_logger.LogInformation("假裝還在埋頭苦干ing…… 其實我去洗杯子了");else_logger.LogInformation("我愛工作,埋頭苦干ing……");await Task.CompletedTask;}/// <summary>/// 關閉前需要完成的工作/// </summary>private void GetOffWork(CancellationToken cancellationToken){_logger.LogInformation("啊,糟糕,有一個緊急 bug 需要下班前完成!!!");_logger.LogInformation("啊啊啊,我愛加班,我要再干 20 秒,Wait 1 ");Task.Delay(TimeSpan.FromSeconds(20)).Wait();_logger.LogInformation("啊啊啊啊啊啊,我愛加班,我要再干 1 分鐘,Wait 2 ");Task.Delay(TimeSpan.FromMinutes(1)).Wait();_logger.LogInformation("啊哈哈哈哈哈,終于好了,下班走人!");}public override Task StopAsync(CancellationToken cancellationToken){_logger.LogInformation("太好了,下班時間到了,output from StopAsync at: {time}", DateTimeOffset.Now);_isStopping = true;_logger.LogInformation("去洗洗茶杯先……", DateTimeOffset.Now);Task.Delay(30_000).Wait();_logger.LogInformation("茶杯洗好了。", DateTimeOffset.Now);_logger.LogInformation("下班嘍 ^_^", DateTimeOffset.Now);return base.StopAsync(cancellationToken);} }重新運行一下
dotnet build dotnet run然后按?CTRL+C?鍵關閉服務,看看運行結果是什么?
我們可以觀察到在 Worker Service 啟動和關閉時,基類?BackgroundService?中可重寫的三個方法的運行順序分別如下圖所示:
總結
在本文中,我通過一個實例介紹了如何優雅退出 Worker Service 的相關知識。
Worker Service 本質上仍是一個控制臺應用程序,執行一個作業。但它不僅可以作為控制臺應用程序直接運行,也可以使用 sc.exe 實用工具安裝為 Windows 服務,還可以部署到 linux 機器上作為后臺進程運行。以后有時間我會介紹更多關于 Worker Service 的知識。
您可以從 GitHub?下載本文中的源碼[2]。
相關鏈接:
https://mp.weixin.qq.com/s/ujGkb5oaXq3lqX_g_eQ3_g?.NET Worker Service 入門介紹???
https://github.com/ITTranslate/WorkerServiceGracefullyShutdown?源碼下載???
作者 :技術譯民
出品 :技術譯站(https://ITTranslator.cn/)
END
總結
以上是生活随笔為你收集整理的.NET Worker Service 如何优雅退出的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: ASP.Net服务性能优化原则
 - 下一篇: 记一次 .NET 车联网云端服务 CPU