想写好中间件,这是基础。
?
一、前言
今天这个内容,基于于ASP.NET Core 3.x。
从3.x开始,ASP.NET Core使用了通用主机模式。它将WebHostBuilder 放到了通用的IHost 之上,这样可以确保Kestrel 可以运行在IHostedService 中。
我们今天就来研究一下这个启动方式和启动顺序。
????为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13636641.html
二、通常的启动次序
通常情况下,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; ????} ????StopAsync(CancellationToken?cancellationToken) ????{ ????????_logger.LogInformation("Stopping?IHostedService?registered?in?Startup"); ????????return?Task.CompletedTask; ????} }
我们做一个简单的IHostedService 。希望加到Startup.cs 中:
Startup { ????public?void?ConfigureServices(IServiceCollection?services) ????{ ????????services.AddHostedService<StartupHostedService>(); ????} }
运行代码:
info:?demo.StartupHostedService[0]????????????#?这是上边的StartupHostedService ??????Starting?IHostedService?registered?in?Startup info:?Microsoft.Hosting.Lifetime[#?这是GenericWebHostSevice ??????Now?listening?on:?https: 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() 时,运行次序取决于它被加入的次序。
看例子:
Service1?:?IHostedService { ????Service1(ILogger<Service1>?logger) ????{ ????????_logger?=?logger; ????} ????"Starting?Service1"); ????????"Stoping?Service1"); ????????return?Task.CompletedTask; ????} } Service2?:?IHostedService { ????Service2(ILogger<Service2>?logger) ????{ ????????_logger?=?logger; ????} ????"Starting?Service2"); ????????"Stoping?Service2"); ????????return?Task.CompletedTask; ????} }
Startup.cs :
ConfigureServices(IServiceCollection?services) ????{ ????????services.AddHostedService<Service1>(); ????????services.AddHostedService<Service2>(); ????} }
运行:
info:?demo.Service1[0]????????????????#?这是Service1 ??????Starting?Service1 info:?demo.Service2[#?这是Service2 ??????Starting?Service2 info:?Microsoft.Hosting.Lifetime[0]????????0] ??????Application?started.?Press?Ctrl+C?to?shut?down.
?
那么,GenericWebHostSevice 是什么时候注册的?
我们看看另一个文件Program.cs :
Program { ????static?Main(string[]?args) ????{ ????????CreateHostBuilder(args).Build().Run(); ????} ????static?IHostBuilder?CreateHostBuilder(string[]?args)?=> ????????Host.CreateDefaultBuilder(args) ????????????.ConfigureWebHostDefaults(webBuilder?=>????????????#?这是GenericWebHostSevice注册的位置 ????????????{ ????????????????webBuilder.UseStartup<Startup>(); ????????????}); }
ConfigureWebHostDefaults 扩展方法调用ConfigureWebHost 方法,该方法执行Startup.ConfigureServices() ,然后注册GenericWebHostService 。整理一下代码,就是下面这个样子:
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() :
ProgramHostedService?:?IHostedService { ????ProgramHostedService(ILogger<ProgramHostedService>?logger) ????{ ????????_logger?=?logger; ????} ????"Starting?ProgramHostedService?registered?in?Program"); ????????"Stopping?ProgramHostedService?registered?in?Program"); ????????return?Task.CompletedTask; ????} }
加到Program.cs 中:
string[]?args) ????{ ????????CreateHostBuilder(args).Build().Run(); ????}
????string[]?args)?=> ????????Host.CreateDefaultBuilder(args) ????????????.ConfigureWebHostDefaults(webBuilder?=>????????????#?这是GenericWebHostSevice注册的位置 ????????????{ ????????????????webBuilder.UseStartup<Startup>(); ????????????}) ????????????.ConfigureServices(services?=>? ????????????????services.AddHostedService<ProgramHostedService>());????????????#?这是ProgramHostedService注册的位置 }
看输出:
#?这是StartupHostedService ??????Starting?IHostedService?registered?//localhost:5001 info:?demo.ProgramHostedService[#?这是ProgramHostedService ??????Starting?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... demo.ProgramHostedServiceStopping?ProgramHostedService?registered?in?Program .StartupHostedServiceIHostedService?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
?
?
|