探索 ASP.Net Core 3.0系列五:引入IHostLifetime並弄清Generic Host啓動交互

前言:在本文中,我將介紹如何在通用主機之上從新構建ASP.NET Core 3.0,以及由此帶來的一些好處。 同時也展現了3.0中引入新的抽象類IHostLifetime,並描述了它在管理應用程序(尤爲是worker services)的生命週期中的做用。在文章的後半部分,我會詳細介紹類之間的交互及其在應用程序啓動和關閉期間的角色。 同時也會詳細介紹一般不須要咱們處理的事情,即便不須要關心,可是它對於咱們理解其原理也頗有用!html

 翻譯:Andrew Lock   https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/web

 

探索ASP.NET Core 3.0系列一:新的項目文件、Program.cs和generic host服務器

探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.csapp

探索 ASP.Net Core 3.0系列三:ASP.Net Core 3.0中的Service provider validation異步

探索ASP.Net Core 3.0系列四:在ASP.NET Core 3.0的應用中啓動時運行異步任務async

探索ASP.Net Core 3.0系列六:ASP.NET Core 3.0新特性啓動信息中的結構化日誌ide

1、背景:將ASP.NET Core從新平臺化到通用主機上

(1)ASP.NET Core 3.0的主要特色之一是整個都已基於.NET Generic Host進行了重寫。 .NET Generic Host 是ASP.NET Core 2.1中引入的,是ASP.NET Core使用的現有WebHost的「非Web」版本。 Generic Host容許您在非Web場景中重用Microsoft.Extensions的許多DI,配置和日誌記錄抽象。函數

(2)雖然這絕對是一個使人羨慕的目標,但在實現中也存在一些問題。 通用主機實質上覆制了ASP.NET Core所需的許多抽象,建立了直接等效項,但使用的是不一樣的命名空間。 IHostingEnvironment是該問題的一個很好的例子-自1.0版以來,它就已經存在於ASP.NET Core中Microsoft.AspNetCore.Hosting中。 可是在版本2.1中,在Microsoft.Extensions.Hosting命名空間中添加了新的IHostingEnvironment。 即便接口是相同的,可是二者都有致使通用庫嘗試使用抽象的問題。oop

(3)使用3.0,ASP.NET Core團隊進行重大更改,直接解決此問題。 他們沒必要從新編寫兩個單獨的Hosts,而是能夠從新編寫ASP.NET Core堆棧,使其位於 .NET generic host之上。 這意味着它能夠真正重用相同的抽象,從而解決了上述問題。 但願在通用主機之上構建其餘非HTTP堆棧(例如ASP.NET Core 3.0中引入的gRPC功能)的部分動機也促成了此舉。ui

(4)可是,對於ASP.NET Core 3在通用主機之上進行「重建」或「從新平臺化」的真正含義是什麼? 從根本上講,這意味着Kestrel Web服務器(處理HTTP請求和對中間件管道的調用)如今做爲IHostedService運行。而當您的應用程序啓動時,Kestrel如今只是在後臺運行的另外一項服務。

 

注意:值得強調的一點是,您在ASP.NET Core 2.x應用程序中使用的現有WebHost和WebHostBuilder實如今3.0中不會消失。 它們再也不是推薦的方法,可是並無被刪除,甚至沒有被標記爲過期。 我但願它們會在下一個主要版本中被標記爲過期,所以值得考慮進行切換。

簡單介紹了背景。 咱們有一個通用主機,而Kestrel做爲IHostedService運行。 可是,ASP.NET Core 3.0中引入的另外一個功能是IHostLifetime接口,該接口容許使用其餘託管模型。

 

2、Worker services 和新的 IHostLifetime 接口

ASP.NET Core 3.0引入了「worker services」的概念以及相關的新應用程序模板。 Worker services旨在爲您提供可長時間運行的應用程序,您能夠將它們安裝爲Windows服務或系統服務。 這些服務有兩個主要功能:

  • 他們經過實現 IHostedService來實現後臺應用。
  • 他們經過一個實現了IHostLifetime接口的類來管理應用程序的生命週期。

下面咱們先來建立一個Worker services,看看長啥樣子:

 

 

 鼠標放到 BackgroundService F12,你會發現,原來如此:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Hosting
{
    //
    // 摘要:
    //     /// Base class for implementing a long running Microsoft.Extensions.Hosting.IHostedService.
    //     ///
    public abstract class BackgroundService : IHostedService, IDisposable
    {
        private Task _executingTask;

        private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();

        //
        // 摘要:
        //     /// This method is called when the Microsoft.Extensions.Hosting.IHostedService
        //     starts. The implementation should return a task that represents /// the lifetime
        //     of the long running operation(s) being performed. ///
        //
        // 參數:
        //   stoppingToken:
        //     Triggered when Microsoft.Extensions.Hosting.IHostedService.StopAsync(System.Threading.CancellationToken)
        //     is called.
        //
        // 返回結果:
        //     A System.Threading.Tasks.Task that represents the long running operations.
        protected abstract Task ExecuteAsync(CancellationToken stoppingToken);

        //
        // 摘要:
        //     /// Triggered when the application host is ready to start the service. ///
        //
        // 參數:
        //   cancellationToken:
        //     Indicates that the start process has been aborted.
        public virtual Task StartAsync(CancellationToken cancellationToken)
        {
            _executingTask = ExecuteAsync(_stoppingCts.Token);
            if (_executingTask.IsCompleted)
            {
                return _executingTask;
            }
            return Task.CompletedTask;
        }

        //
        // 摘要:
        //     /// Triggered when the application host is performing a graceful shutdown. ///
        //
        // 參數:
        //   cancellationToken:
        //     Indicates that the shutdown process should no longer be graceful.
        public virtual async Task StopAsync(CancellationToken cancellationToken)
        {
            if (_executingTask != null)
            {
                try
                {
                    _stoppingCts.Cancel();
                }
                finally
                {
                    await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
                }
            }
        }

        public virtual void Dispose()
        {
            _stoppingCts.Cancel();
        }
    }
}

 

很清晰,BackgroundService 繼承了 IHostedService。IHostedService已經存在了很長時間,而且容許您運行後臺服務。 第二點頗有趣。 IHostLifetime接口是.NET Core 3.0的新增功能,它具備兩種方法:

public interface IHostLifetime
{
    Task WaitForStartAsync(CancellationToken cancellationToken);
    Task StopAsync(CancellationToken cancellationToken);
}

在稍後的部分中,咱們將詳細介紹IHostLifetime,但總結以下:

  • 通用主機啓動時將調用WaitForStartAsync,可將其用於監聽關閉事件或延遲應用程序的啓動,直到發生某些事件爲止。
  • 通用主機中止時調用StopAsync。

.NET Core 3.0當前存在三種不一樣的IHostLifetime實現:

  • ConsoleLifetime –監聽SIGTERM或Ctrl + C並中止主機應用程序。
  • SystemdLifetime –監聽SIGTERM並中止主機應用程序,並通知systemd有關狀態更改(「Ready」和「Stopping」)
  • Windows ServiceLifetime –掛鉤Windows Service事件以進行生命週期管理

默認狀況下,通用主機使用ConsoleLifetime,它提供了您在ASP.NET Core 2.x中慣用的行爲,當應用程序從控制檯接收到SIGTERM信號或Ctrl + C時,應用程序將中止。 建立Worker Service(Windows或systemd服務)時,主要是爲該應用程序配置IHostLifetime。

 

3、理解應用的啓動

當我在研究這種新的抽象時,開始感到很是困惑。 何時會被調用? 它與ApplicationLifetime有什麼關係? 誰首先調用了IHostLifetime? 爲了使事情更清晰,我花了一些時間來查找ASP.NET Core 3.0中默認應用程序中他們之間的交互。

在這篇文章中,咱們從默認的ASP.NET Core 3.0 Program.cs文件開始。

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

特別是,一旦構建了通用Host對象,我會對Run()調用的功能感興趣。

請注意,我不會對代碼進行詳盡的描述-我將跳過任何我認爲可有可無的內容。 個人目標是對交互有一個總體感受。若是您想更深刻一點,能夠 查看源代碼!

Run()是HostingAbstractionsHostExtensions的擴展方法,它調用RunAsync()並阻塞直到該方法退出。 當該方法退出時,應用程序退出,所以全部有趣的事情都在那裏發生了! 下圖概述了RunAsync()中發生的狀況,下面將討論詳細信息:

 

 

 (圖片來自於:https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/

Program.cs調用Run()擴展方法,該方法調用RunAsync()擴展方法。 依次調用IHost實例上的StartAsync()。 StartAsync方法能夠完成諸如啓動IHostingServices(稍後將介紹)之類的許多工做,可是該方法在被調用後會很快返回。

接下來,RunAsync()方法調用另外一個擴展方法,稱爲WaitForShutdownAsync()。 此擴展方法執行圖中所示的全部其餘操做。 這個名字很具描述性。 此方法對其自身進行配置,以使其暫停,直到觸發IHostApplicationLifetime上的ApplicationStopping取消令牌爲止(咱們將很快了解如何觸發該令牌)。

擴展方法WaitForShutdownAsync()使用TaskCompletionSource並等待關聯的Task來實現此目的。 這不是我之前須要使用的模式,它看起來頗有趣,所以我在下面添加了它(改編自HostingAbstractionsHostExtensions)

public static async Task WaitForShutdownAsync(this IHost host)
{
    // Get the lifetime object from the DI container
    var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();

    // Create a new TaskCompletionSource called waitForStop
    var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

    // Register a callback with the ApplicationStopping cancellation token
    applicationLifetime.ApplicationStopping.Register(obj =>
    {
        var tcs = (TaskCompletionSource<object>)obj;

        // When the application stopping event is fired, set 
        // the result for the waitForStop task, completing it
        tcs.TrySetResult(null);
    }, waitForStop);

    // Await the Task. This will block until ApplicationStopping is triggered,
    // and TrySetResult(null) is called
    await waitForStop.Task;

    // We're shutting down, so call StopAsync on IHost
    await host.StopAsync();
}

此擴展方法說明了應用程序如何在運行狀態下「暫停」,而全部內容都在後臺任務中運行。 讓咱們更深刻地瞭解上圖頂部的IHost.StartAsync()方法調用。

 

4、Host.StartAsync()

在上圖中,咱們研究了在接口IHost上運行的HostingAbstractionsHostExtensions擴展方法。 若是咱們想知道在調用IHost.StartAsync()時一般會發生什麼,那麼咱們須要查看一個具體的實現。 下圖顯示了實際使用的通用Host實現的StartAsync()方法。 一樣,咱們來 看看如下有趣的部分。

 

 

 從上圖能夠看到,這裏還有不少步驟! 對Host.StartAsync()的調用是經過在本文前面介紹的IHostLifetime實例上調用WaitForStartAsync()開始的。 此時的行爲取決於您使用的是哪一個IHostLifetime,可是我將假定咱們正在爲本文使用ConsoleLifetime(ASP.NET Core應用程序的默認設置)。

 

注意:SystemdLifetime的行爲與ConsoleLifetime很是類似,並具備一些額外的功能。 WindowsServiceLifetime是徹底不一樣的,而且派生自System.ServiceProcess.ServiceBase。

ConsoleLifetime.WaitForStartAsync()方法(以下所示)作了一件重要的事情:它爲控制檯中的SIGTERM請求和Ctrl + C添加了事件偵聽器。 請求關閉應用程序時將觸發這些事件。 所以,一般由IHostLifetime負責控制應用程序什麼時候關閉。

public Task WaitForStartAsync(CancellationToken cancellationToken)
{
    // ... logging removed for brevity

    // Attach event handlers for SIGTERM and Ctrl+C
    AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
    Console.CancelKeyPress += OnCancelKeyPress;

    // Console applications start immediately.
    return Task.CompletedTask;
}

 

如上面的代碼所示,此方法當即完成,並將控制權返回給Host.StartAsync()。 此時,主機加載全部IHostedService實例,並在每一個實例上調用StartAsync()。 這包括用於啓動Kestrel Web服務器的GenericWebHostService(該服務器最後啓動)。

一旦全部IHostedServices已啓動,Host.StartAsync()就會調用IHostApplicationLifetime.NotifyStarted()來觸發全部已註冊的回調(一般只是記錄)並退出。

請注意,IhostLifetime與IHostApplicationLifetime不一樣。 前者包含用於控制應用程序啓動時間的邏輯。 後者(由ApplicationLifetime實現)包含CancellationTokens,您能夠根據它們註冊回調以在應用程序生命週期的各個時間點運行。

 

此時,應用程序處於「運行」狀態,全部後臺服務都在運行,Kestrel處理請求,而且原始的WaitForShutdownAsync()擴展方法等待ApplicationStopping事件觸發。 最後,讓咱們看一下在控制檯中鍵入Ctrl + C時會發生什麼。

 

5、shutdown process

當ConsoleLifetime從控制檯接收到SIGTERM信號或Ctrl + C(取消鍵)時,將發生關閉過程。 下圖顯示了關閉過程當中全部關鍵參與者之間的相互做用:

 

 (1)觸發Ctrl + C終止事件時,ConsoleLifetime會調用IHostApplicationLifetime.StopApplication()方法。 這將觸發全部使用ApplicationStopping取消令牌註冊的回調。 若是回頭看一下程序概述,您將看到觸發器是原始RunAsync()擴展方法正在等待的觸發器,所以等待的任務完成,並調用了Host.StopAsync()。

(2)Host.StopAsync()開始經過再次調用IHostApplicationLifetime.StopApplication()。 第二次調用是第二次運行時的noop,但這是必需的,由於從技術上講,還有其餘觸發Host.StopAsync()的方式。

(3)接下來,主機以相反的順序關閉全部IHostedServices。 首先啓動的服務將最後中止,所以GenericWebHostedService首先被關閉。

(4)關閉服務後,將調用IHostLifetime.StopAsync,它對於ConsoleLifetime是noop(空操做)。 最後,Host.StopAsync()在退出以前調用IHostApplicationLifetime.NotifyStopped()以通知任何關聯的處理程序。

(5)此時,一切都關閉,Program.Main函數退出,應用程序退出。

 

6、總結

在這篇文章中,聊了一些有關如何在通用主機之上從新構建ASP.NET Core 3.0的背景,並介紹了新的IHostLifetime接口。 而後,我詳細描述了使用通用主機的典型ASP.NET Core 3.0應用程序的啓動和關閉所涉及的各類類和接口之間的交互。若是仍是不明白的同窗能夠查看源碼,但願它對你有所幫助!

 

 

翻譯:Andrew Lock   https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/

 

做者:郭崢

出處:http://www.cnblogs.com/runningsmallguo/

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

相關文章
相關標籤/搜索