想写好中间件,这是基础。css
今天这个内容,基于于ASP.NET Core 3.x。html
从3.x开始,ASP.NET Core使用了通用主机模式。它将WebHostBuilder
放到了通用的IHost
之上,这样能够确保Kestrel
能够运行在IHostedService
中。git
咱们今天就来研究一下这个启动方式和启动顺序。github
为了防止不提供原网址的转载,特在这里加上原文连接:http://www.javashuo.com/article/p-bficxesq-ng.htmlweb
一般状况下,IHostedService
的任何实如今添加到Startup.ConfigureServices()
后,都会在GenericWebHostService
以前启动。c#
这是微软官方给出的图。服务器
这个图展现了在IHost
上调用RunAsync()
时的启动顺序(后者又调用StartAsync()
)。对咱们来讲,最重要的部分是启动的IHostedServices
。从图上也能够看到,自定义IHostedServices
先于GenericWebHostSevice
启动。微信
咱们来看一个简单的例子:app
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
中:ui
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<StartupHostedService>();
}
}
运行代码:
info: demo.StartupHostedService[0] # 这是上边的StartupHostedService
Starting IHostedService registered in Startup
info: Microsoft.Hosting.Lifetime[0] # 这是GenericWebHostSevice
Now 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
在最后启动?
先看看多个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] # 这是Service1
Starting Service1
info: demo.Service2[0] # 这是Service2
Starting Service2
info: Microsoft.Hosting.Lifetime[0] # 这是GenericWebHostSevice
Now 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
后面启动。
在大多数状况下,在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] # 这是StartupHostedService
Starting IHostedService registered in Startup
info: Microsoft.Hosting.Lifetime[0] # 这是GenericWebHostSevice
Now listening on: https://localhost:5001
info: demo.ProgramHostedService[0] # 这是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...
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
![]() |
微信公众号:老王Plus 扫描二维码,关注我的公众号,能够第一时间获得最新的我的文章和内容推送 本文版权归做者全部,转载请保留此声明和原文连接 |