ASP.NET Core 3.x控制IHostedService启动顺序浅探
想寫好中間件,這是基礎。
?
一、前言
今天這個內容,基于于ASP.NET Core 3.x。
從3.x開始,ASP.NET Core使用了通用主機模式。它將WebHostBuilder放到了通用的IHost之上,這樣可以確保Kestrel可以運行在IHostedService中。
我們今天就來研究一下這個啟動方式和啟動順序。
二、通常的啟動次序
通常情況下,IHostedService的任何實現在添加到Startup.ConfigureServices()后,都會在GenericWebHostService之前啟動。
這是微軟官方給出的圖。
這個圖展示了在IHost上調用RunAsync()時的啟動順序(后者又調用StartAsync())。對我們來說,最重要的部分是啟動的IHostedServices。從圖上也可以看到,自定義IHostedServices先于GenericWebHostSevice啟動。
我們來看一個簡單的例子:
public?class?StartupHostedService?:?IHostedService {private?readonly?ILogger?_logger;public?StartupHostedService(ILogger<StartupHostedService>?logger){_logger?=?logger;}public?Task?StartAsync(CancellationToken?cancellationToken){_logger.LogInformation("Starting?IHostedService?registered?in?Startup");return?Task.CompletedTask;}public?Task?StopAsync(CancellationToken?cancellationToken){_logger.LogInformation("Stopping?IHostedService?registered?in?Startup");return?Task.CompletedTask;} }我們做一個簡單的IHostedService。希望加到Startup.cs中:
public?class?Startup {public?void?ConfigureServices(IServiceCollection?services){services.AddHostedService<StartupHostedService>();} }運行代碼:
info:?demo.StartupHostedService[0]????????????#?這是上邊的StartupHostedServiceStarting?IHostedService?registered?in?Startup info:?Microsoft.Hosting.Lifetime[0]????????????#?這是GenericWebHostSeviceNow?listening?on:?https://localhost:5001 info:?Microsoft.Hosting.Lifetime[0]Application?started.?Press?Ctrl+C?to?shut?down.正如預期的那樣,IHostedService首先執行,然后是GenericWebHostSevice。ApplicationLifetime事件在所有IHostedServices執行之后觸發。無論在什么地方注冊了Startup.ConfigureServices()中的IHostedService,?GenericWebHostSevice都在最后啟動。
?
那么問題來了,為什么GenericWebHostSevice在最后啟動?
三、為什么`GenericWebHostSevice`在最后啟動?
先看看多個IHostedService的情況。
當有多個IHostedService的實現加入到Startup.ConfigureServices()時,運行次序取決于它被加入的次序。
看例子:
public?class?Service1?:?IHostedService {private?readonly?ILogger?_logger;public?Service1(ILogger<Service1>?logger){_logger?=?logger;}public?Task?StartAsync(CancellationToken?cancellationToken){_logger.LogInformation("Starting?Service1");return?Task.CompletedTask;}public?Task?StopAsync(CancellationToken?cancellationToken){_logger.LogInformation("Stoping?Service1");return?Task.CompletedTask;} } public?class?Service2?:?IHostedService {private?readonly?ILogger?_logger;public?Service2(ILogger<Service2>?logger){_logger?=?logger;}public?Task?StartAsync(CancellationToken?cancellationToken){_logger.LogInformation("Starting?Service2");return?Task.CompletedTask;}public?Task?StopAsync(CancellationToken?cancellationToken){_logger.LogInformation("Stoping?Service2");return?Task.CompletedTask;} }Startup.cs:
public?class?Startup {public?void?ConfigureServices(IServiceCollection?services){services.AddHostedService<Service1>();services.AddHostedService<Service2>();} }運行:
info:?demo.Service1[0]????????????????#?這是Service1Starting?Service1 info:?demo.Service2[0]????????????????#?這是Service2Starting?Service2 info:?Microsoft.Hosting.Lifetime[0]????????#?這是GenericWebHostSeviceNow?listening?on:?https://localhost:5001 info:?Microsoft.Hosting.Lifetime[0]Application?started.?Press?Ctrl+C?to?shut?down.?
那么,GenericWebHostSevice是什么時候注冊的?
我們看看另一個文件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?=>????????????#?這是GenericWebHostSevice注冊的位置{webBuilder.UseStartup<Startup>();}); }ConfigureWebHostDefaults擴展方法調用ConfigureWebHost方法,該方法執行Startup.ConfigureServices(),然后注冊GenericWebHostService。整理一下代碼,就是下面這個樣子:
public?static?IHostBuilder?ConfigureWebHost(this?IHostBuilder?builder,?Action<IWebHostBuilder>?configure) {var?webhostBuilder?=?new?GenericWebHostBuilder(builder);configure(webhostBuilder);builder.ConfigureServices((context,?services)?=>?services.AddHostedService<GenericWebHostService>());return?builder; }這樣可以確保GenericWebHostService總是最后運行,以保持通用主機實現和WebHost(已棄用)實現之間的行為一致。
?
因此,可以采用同樣的方式,讓IHostedService在GenericWebHostService后面啟動。
四、讓`IHostedService`在`GenericWebHostService`后面啟動
在大多數情況下,在GenericWebHostService之前啟動IHostedServices就可以滿足常規的應用。但是,GenericWebHostService還負責構建應用程序的中間件管道。如果IHostedService依賴于中間件管道或路由,那么就需要將它的啟動延遲到GenericWebHostService完成之后。
根據上面的說明,在GenericWebHostService之后執行IHostedService的唯一方法是將它添加到GenericWebHostService之后的DI容器中。這意味著你必須跳出Startup.ConfigureServices(),在調用ConfigureWebHostDefaults之后,直接在IHostBuilder上調用ConfigureServices():
public?class?ProgramHostedService?:?IHostedService {private?readonly?ILogger?_logger;public?ProgramHostedService(ILogger<ProgramHostedService>?logger){_logger?=?logger;}public?Task?StartAsync(CancellationToken?cancellationToken){_logger.LogInformation("Starting?ProgramHostedService?registered?in?Program");return?Task.CompletedTask;}public?Task?StopAsync(CancellationToken?cancellationToken){_logger.LogInformation("Stopping?ProgramHostedService?registered?in?Program");return?Task.CompletedTask;} }加到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?=>????????????#?這是GenericWebHostSevice注冊的位置{webBuilder.UseStartup<Startup>();}).ConfigureServices(services?=>?services.AddHostedService<ProgramHostedService>());????????????#?這是ProgramHostedService注冊的位置 }看輸出:
info:?demo.StartupHostedService[0]????????????#?這是StartupHostedServiceStarting?IHostedService?registered?in?Startup info:?Microsoft.Hosting.Lifetime[0]????????????#?這是GenericWebHostSeviceNow?listening?on:?https://localhost:5001 info:?demo.ProgramHostedService[0]????????????#?這是ProgramHostedServiceStarting?ProgramHostedService?registered?in?Program info:?Microsoft.Hosting.Lifetime[0]Application?started.?Press?Ctrl+C?to?shut?down.同樣,在關閉應用時,IHostedServices被反向停止,所以ProgramHostedService首先停止,接著是GenericWebHostSevice,最后是StartupHostedService:
info:?Microsoft.Hosting.Lifetime[0]Application?is?shutting?down... info:?demo.ProgramHostedService[0]Stopping?ProgramHostedService?registered?in?Program info:?demo.StartupHostedService[0]Stopping?IHostedService?registered?in?Startup五、總結
最后總結一下:
IHostedServices的執行順序與它們在Startup.configureservices()中添加到DI容器中的順序相同。運行偵聽HTTP請求的Kestrel服務器的GenericWebHostSevice總是注冊的IHostedServices之后運行。
要在GenericWebHostSevice之后啟動IHostedService,需要在Program.cs中的IHostBuilder上的ConfigureServices()擴展方法中進行注冊。
(全文完)
本文的代碼在:https://github.com/humornif/Demo-Code/tree/master/0024/demo
總結
以上是生活随笔為你收集整理的ASP.NET Core 3.x控制IHostedService启动顺序浅探的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core 下的爬虫利器
- 下一篇: asp.net ajax控件工具集 Au