前言:在本文中,我將介紹如何在通用主機之上重新構建ASP.NET Core 3.0,以及由此帶來的一些好處。 同時也展示了3.0中引入新的抽象類IHostLifetime,並描述了它在管理應用程序(尤其是worker services)的生命周期中的作用。在文章的后半部分,我會詳細介紹類之間的交互及其在應用程序啟動和關閉期間的角色。 同時也會詳細介紹通常不需要我們處理的事情,即使不需要關心,但是它對於我們理解其原理也很有用!
翻譯:Andrew Lock https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/
探索ASP.NET Core 3.0系列一:新的項目文件、Program.cs和generic host
探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs
探索 ASP.Net Core 3.0系列三:ASP.Net Core 3.0中的Service provider validation
探索ASP.Net Core 3.0系列四:在ASP.NET Core 3.0的應用中啟動時運行異步任務
探索ASP.Net Core 3.0系列六:ASP.NET Core 3.0新特性啟動信息中的結構化日志
一、背景:將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。 即使接口是相同的,但是兩者都有導致通用庫嘗試使用抽象的問題。
(3)使用3.0,ASP.NET Core團隊進行重大更改,直接解決此問題。 他們不必重新編寫兩個單獨的Hosts,而是可以重新編寫ASP.NET Core堆棧,使其位於 .NET generic host之上。 這意味着它可以真正重用相同的抽象,從而解決了上述問題。 希望在通用主機之上構建其他非HTTP堆棧(例如ASP.NET Core 3.0中引入的gRPC功能)的部分動機也促成了此舉。
(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接口,該接口允許使用其他托管模型。
二、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。
三、理解應用的啟動
當我在研究這種新的抽象時,開始感到非常困惑。 什么時候會被調用? 它與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()中發生的情況,下面將討論詳細信息:
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()方法調用。
四、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時會發生什么。
五、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函數退出,應用程序退出。
六、總結
在這篇文章中,聊了一些有關如何在通用主機之上重新構建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/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。