ASP.NET Core 3.x控制IHostedService启动顺序浅探
想寫好中間件,這是基礎(chǔ)。
?
一、前言
今天這個(gè)內(nèi)容,基于于ASP.NET Core 3.x。
從3.x開(kāi)始,ASP.NET Core使用了通用主機(jī)模式。它將WebHostBuilder放到了通用的IHost之上,這樣可以確保Kestrel可以運(yùn)行在IHostedService中。
我們今天就來(lái)研究一下這個(gè)啟動(dòng)方式和啟動(dòng)順序。
二、通常的啟動(dòng)次序
通常情況下,IHostedService的任何實(shí)現(xiàn)在添加到Startup.ConfigureServices()后,都會(huì)在GenericWebHostService之前啟動(dòng)。
這是微軟官方給出的圖。
這個(gè)圖展示了在IHost上調(diào)用RunAsync()時(shí)的啟動(dòng)順序(后者又調(diào)用StartAsync())。對(duì)我們來(lái)說(shuō),最重要的部分是啟動(dòng)的IHostedServices。從圖上也可以看到,自定義IHostedServices先于GenericWebHostSevice啟動(dòng)。
我們來(lái)看一個(gè)簡(jiǎn)單的例子:
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;} }我們做一個(gè)簡(jiǎn)單的IHostedService。希望加到Startup.cs中:
public?class?Startup {public?void?ConfigureServices(IServiceCollection?services){services.AddHostedService<StartupHostedService>();} }運(yùn)行代碼:
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.正如預(yù)期的那樣,IHostedService首先執(zhí)行,然后是GenericWebHostSevice。ApplicationLifetime事件在所有IHostedServices執(zhí)行之后觸發(fā)。無(wú)論在什么地方注冊(cè)了Startup.ConfigureServices()中的IHostedService,?GenericWebHostSevice都在最后啟動(dòng)。
?
那么問(wèn)題來(lái)了,為什么GenericWebHostSevice在最后啟動(dòng)?
三、為什么`GenericWebHostSevice`在最后啟動(dòng)?
先看看多個(gè)IHostedService的情況。
當(dāng)有多個(gè)IHostedService的實(shí)現(xiàn)加入到Startup.ConfigureServices()時(shí),運(yùn)行次序取決于它被加入的次序。
看例子:
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>();} }運(yùn)行:
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是什么時(shí)候注冊(cè)的?
我們看看另一個(gè)文件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注冊(cè)的位置{webBuilder.UseStartup<Startup>();}); }ConfigureWebHostDefaults擴(kuò)展方法調(diào)用ConfigureWebHost方法,該方法執(zhí)行Startup.ConfigureServices(),然后注冊(cè)GenericWebHostService。整理一下代碼,就是下面這個(gè)樣子:
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總是最后運(yùn)行,以保持通用主機(jī)實(shí)現(xiàn)和WebHost(已棄用)實(shí)現(xiàn)之間的行為一致。
?
因此,可以采用同樣的方式,讓IHostedService在GenericWebHostService后面啟動(dòng)。
四、讓`IHostedService`在`GenericWebHostService`后面啟動(dòng)
在大多數(shù)情況下,在GenericWebHostService之前啟動(dòng)IHostedServices就可以滿足常規(guī)的應(yīng)用。但是,GenericWebHostService還負(fù)責(zé)構(gòu)建應(yīng)用程序的中間件管道。如果IHostedService依賴于中間件管道或路由,那么就需要將它的啟動(dòng)延遲到GenericWebHostService完成之后。
根據(jù)上面的說(shuō)明,在GenericWebHostService之后執(zhí)行IHostedService的唯一方法是將它添加到GenericWebHostService之后的DI容器中。這意味著你必須跳出Startup.ConfigureServices(),在調(diào)用ConfigureWebHostDefaults之后,直接在IHostBuilder上調(diào)用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注冊(cè)的位置{webBuilder.UseStartup<Startup>();}).ConfigureServices(services?=>?services.AddHostedService<ProgramHostedService>());????????????#?這是ProgramHostedService注冊(cè)的位置 }看輸出:
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.同樣,在關(guān)閉應(yīng)用時(shí),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五、總結(jié)
最后總結(jié)一下:
IHostedServices的執(zhí)行順序與它們?cè)赟tartup.configureservices()中添加到DI容器中的順序相同。運(yùn)行偵聽(tīng)HTTP請(qǐng)求的Kestrel服務(wù)器的GenericWebHostSevice總是注冊(cè)的IHostedServices之后運(yùn)行。
要在GenericWebHostSevice之后啟動(dòng)IHostedService,需要在Program.cs中的IHostBuilder上的ConfigureServices()擴(kuò)展方法中進(jìn)行注冊(cè)。
(全文完)
本文的代碼在:https://github.com/humornif/Demo-Code/tree/master/0024/demo
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 3.x控制IHostedService启动顺序浅探的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .NET Core 下的爬虫利器
- 下一篇: Istio Pilot 源码分析(一)