想寫好中間件,這是基礎。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 掃描二維碼,關注我的公衆號,能夠第一時間獲得最新的我的文章和內容推送 本文版權歸做者全部,轉載請保留此聲明和原文連接 |