<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          .NET Worker Service 如何優(yōu)雅退出

          共 8436字,需瀏覽 17分鐘

           ·

          2021-06-25 08:14


          上一篇文章中我們了解了 .NET 中的 Worker Service 入門介紹[1],今天我們接著介紹一下如何優(yōu)雅地關(guān)閉和退出 Worker Service。

          Worker 類

          上一篇文章中,我們已經(jīng)知道了 Worker Service 模板為我們提供三個(gè)開箱即用的核心文件,其中 Worker 類是繼承自抽象基類 BackgroundService 的,而 BackgroundService 實(shí)現(xiàn)了 IHostedService 接口。最終 Worker 類會(huì)被注冊(cè)為托管服務(wù),我們處理任務(wù)的核心代碼就是寫在 Worker 類中的。所以,我們需要重點(diǎn)了解一下 Worker 及其基類。

          先來(lái)看看它的基類 BackgroundService :

          基類 BackgroundService 中有三個(gè)可重寫的方法,可以讓我們綁定到應(yīng)用程序的生命周期中:

          • 抽象方法 ExecuteAsync:作為應(yīng)用程序主要入口點(diǎn)的方法。如果此方法退出,則應(yīng)用程序?qū)㈥P(guān)閉。我們必須在 Worker 中實(shí)現(xiàn)它。

          • 虛方法 StartAsync:在應(yīng)用程序啟動(dòng)時(shí)調(diào)用。如果需要,可以重寫此方法,它可用于在服務(wù)啟動(dòng)時(shí)一次性地設(shè)置資源;當(dāng)然,也可以忽略它。

          • 虛方法 StopAsync:在應(yīng)用程序關(guān)閉時(shí)調(diào)用。如果需要,可以重寫此方法,在關(guān)閉時(shí)釋放資源和銷毀對(duì)象;當(dāng)然,也可以忽略它。

          默認(rèn)情況下 Worker 只重寫必要的抽象方法 ExecuteAsync

          新建一個(gè) Worker Service 項(xiàng)目

          我們來(lái)新建一個(gè) Worker Service,使用 Task.Delay 來(lái)模擬關(guān)閉前必須完成的一些操作,看看是否可以通過(guò)簡(jiǎn)單地在 ExecuteAsync 中 Delay 來(lái)模擬實(shí)現(xiàn)優(yōu)雅關(guān)閉。

          需要用到的開發(fā)工具:

          • Visual Studio Code:https://code.visualstudio.com/

          • 最新的 .NET SDK:https://dotnet.microsoft.com/download

          安裝好以上工具后,在終端中運(yùn)行以下命令,創(chuàng)建一個(gè) Worker Service 項(xiàng)目:

          dotnet new Worker -n "MyService"

          創(chuàng)建好 Worker Service 后,在 Visual Studio Code 中打開應(yīng)用程序,然后構(gòu)建并運(yùn)行一下,以確保一切正常:

          dotnet build
          dotnet run

          按 CTRL+C 鍵關(guān)閉服務(wù),服務(wù)會(huì)立即退出,默認(rèn)情況下 Worker Service 的關(guān)閉就是這么直接!在很多場(chǎng)景(比如內(nèi)存中的隊(duì)列)中,這不是我們想要的結(jié)果,有時(shí)我們不得不在服務(wù)關(guān)閉前完成一些必要的資源回收或事務(wù)處理

          我們看一下 Worker 類的代碼,會(huì)看到它只重寫了基類 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);
          }
          }

          我們嘗試修改一下此方法,退出前做一些業(yè)務(wù)處理:

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

          然后測(cè)試一下,看它是不是會(huì)像我們預(yù)期的那樣先等待 60 秒再關(guān)閉。

          dotnet build
          dotnet run

          按 CTRL+C 鍵關(guān)閉服務(wù),我們會(huì)發(fā)現(xiàn),它在輸出 “等待退出” 后,并沒有等待 60 秒并輸出 “退出” 之后再關(guān)閉,而是很快便退出了。這就像我們熟悉的控制臺(tái)應(yīng)用程序,默認(rèn)情況下,在我們點(diǎn)了右上角的關(guān)閉按鈕或者按下 CTRL+C 鍵時(shí),會(huì)直接關(guān)閉一樣。

          Worker Service 優(yōu)雅退出

          那么,怎么才能實(shí)現(xiàn)優(yōu)雅退出呢?

          方法其實(shí)很簡(jiǎn)單,那就是將 IHostApplicationLifetime 注入到我們的服務(wù)中,然后在應(yīng)用程序停止時(shí)手動(dòng)調(diào)用 IHostApplicationLifetime 的 StopApplication 方法來(lái)關(guān)閉應(yīng)用程序。

          修改 Worker 的構(gòu)造函數(shù),注入 IHostApplicationLifetime

          private readonly IHostApplicationLifetime _hostApplicationLifetime;
          private readonly ILogger<Worker> _logger;

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

          然后在 ExecuteAsync 中,處理完退出前必須完成的業(yè)務(wù)邏輯后,手動(dòng)調(diào)用 IHostApplicationLifetime 的 StopApplication 方法,下面是豐富過(guò)的 ExecuteAsync 代碼:

          protected override async Task ExecuteAsync(CancellationToken stoppingToken)
          {
          try
          {
          // 這里實(shí)現(xiàn)實(shí)際的業(yè)務(wù)邏輯
          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); //關(guān)閉前需要完成的工作
          _hostApplicationLifetime.StopApplication(); //手動(dòng)調(diào)用 StopApplication
          }
          }

          private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken)
          {
          _logger.LogInformation("我愛工作,埋頭苦干ing……");
          await Task.CompletedTask;
          }

          /// <summary>
          /// 關(guān)閉前需要完成的工作
          /// </summary>
          private void GetOffWork(CancellationToken cancellationToken)
          {
          _logger.LogInformation("啊,糟糕,有一個(gè)緊急 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("啊哈哈哈哈哈,終于好了,下班走人!");
          }

          此時(shí),再次 dotnet run 運(yùn)行服務(wù),然后按 CTRL+C 鍵關(guān)閉服務(wù),您會(huì)發(fā)現(xiàn)關(guān)閉前需要完成的工作 GetOffWork 運(yùn)行完成后才會(huì)退出服務(wù)了。

          至此,我們已經(jīng)實(shí)現(xiàn)了 Worker Service 的優(yōu)雅退出。

          StartAsync 和 StopAsync

          為了更進(jìn)一步了解 Worker Service,我們?cè)賮?lái)豐富一下我們的代碼,重寫基類 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
          {
          // 這里實(shí)現(xiàn)實(shí)際的業(yè)務(wù)邏輯
          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); //關(guān)閉前需要完成的工作
          _hostApplicationLifetime.StopApplication(); //手動(dòng)調(diào)用 StopApplication
          }
          }

          private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken)
          {
          if (_isStopping)
          _logger.LogInformation("假裝還在埋頭苦干ing…… 其實(shí)我去洗杯子了");
          else
          _logger.LogInformation("我愛工作,埋頭苦干ing……");

          await Task.CompletedTask;
          }

          /// <summary>
          /// 關(guān)閉前需要完成的工作
          /// </summary>
          private void GetOffWork(CancellationToken cancellationToken)
          {
          _logger.LogInformation("啊,糟糕,有一個(gè)緊急 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("太好了,下班時(shí)間到了,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);
          }
          }

          重新運(yùn)行一下

          dotnet build
          dotnet run

          然后按 CTRL+C 鍵關(guān)閉服務(wù),看看運(yùn)行結(jié)果是什么?

          我們可以觀察到在 Worker Service 啟動(dòng)和關(guān)閉時(shí),基類 BackgroundService 中可重寫的三個(gè)方法的運(yùn)行順序分別如下圖所示:

          總結(jié)

          在本文中,我通過(guò)一個(gè)實(shí)例介紹了如何優(yōu)雅退出 Worker Service 的相關(guān)知識(shí)。

          Worker Service 本質(zhì)上仍是一個(gè)控制臺(tái)應(yīng)用程序,執(zhí)行一個(gè)作業(yè)。但它不僅可以作為控制臺(tái)應(yīng)用程序直接運(yùn)行,也可以使用 sc.exe 實(shí)用工具安裝為 Windows 服務(wù),還可以部署到 linux 機(jī)器上作為后臺(tái)進(jìn)程運(yùn)行。以后有時(shí)間我會(huì)介紹更多關(guān)于 Worker Service 的知識(shí)。

          您可以從 GitHub 下載本文中的源碼[2]


          相關(guān)鏈接:

          1. https://mp.weixin.qq.com/s/ujGkb5oaXq3lqX_g_eQ3_g .NET Worker Service 入門介紹 ??

          2. https://github.com/ITTranslate/WorkerServiceGracefullyShutdown 源碼下載 ??


          作者 :技術(shù)譯民
          出品 :技術(shù)譯站(https://ITTranslator.cn/)


          往期精彩回顧




          【推薦】.NET Core開發(fā)實(shí)戰(zhàn)視頻課程 ★★★

          .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門篇-開篇及總體規(guī)劃

          【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴注入與動(dòng)態(tài)選擇看這篇就夠了

          10個(gè)小技巧助您寫出高性能的ASP.NET Core代碼

          用abp vNext快速開發(fā)Quartz.NET定時(shí)任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說(shuō)法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

          C#異步編程看這篇就夠了


          瀏覽 57
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人做爰黄AA片免费看三区 | 青青草婷婷 | 日韩国产无码1区2区3区4区 | 男人天堂综合网 | 爱爱网址 |