在上一篇文章中介紹了CreateHostBuilder 這個方法的作用以及內(nèi)部實(shí)現(xiàn),今天這篇文章我們接著講一下代碼中的下一步執(zhí)行過程,即對IHostBuilder的實(shí)例調(diào)用Build方法。
Build主機(jī)
當(dāng)程序?qū)χ鳈C(jī)設(shè)置完成后,下一步就是Build主機(jī),Build的實(shí)現(xiàn)在HostBuilder類中
public IHost Build()
{
if (_hostBuilt)
{
throw new InvalidOperationException("Build can only be called once.");
}
_hostBuilt = true;
BuildHostConfiguration();
CreateHostingEnvironment();
CreateHostBuilderContext();
BuildAppConfiguration();
CreateServiceProvider();
return _appServices.GetRequiredService<IHost>();
}
上一篇文章中我們講過HostBuilder類聲明一些委托集合,在配置主機(jī)的過成功用戶代碼和ASP.NET Core源碼都添加了一些委托在這個集合中,BuildHostConfiguration 及BuildAppConfiguration 就是循環(huán)對應(yīng)的委托結(jié)合并執(zhí)行委托方法,在CreateServiceProvider 里把一些服務(wù)(Service)添加到ServiceCollection里,然后創(chuàng)建.NET內(nèi)置的服務(wù)容器(service container),然后在class的構(gòu)造函數(shù)里就可以得到這些服務(wù)的實(shí)例,這就是.NET 內(nèi)置的依賴注入(DI),CreateServiceProvider 方法里注冊了幾個重要的服務(wù)
- IHostApplicationLifetime
- IHostLifetime
- IHostEnviornment
這些服務(wù)會在啟動主機(jī)即調(diào)用Run的時候用到。
啟動(Run)主機(jī)
在配置完主機(jī)后就可以調(diào)用Run方法啟動主機(jī)服務(wù)了,Run 方法是IHost的一個擴(kuò)展方法, 它調(diào)用RunAsync方法啟動主機(jī)并阻止調(diào)用線程直到方法結(jié)束,方法結(jié)束的時候應(yīng)用程序也就結(jié)束了。RunAsync里有兩行主要的代碼
await host.StartAsync(token).ConfigureAwait(false);
await host.WaitForShutdownAsync(token).ConfigureAwait(false);
StartAsync()做了很多事,其中最重要的就是啟動IHostService,然后程序執(zhí)行完后會快速的返回,接著調(diào)用另一個擴(kuò)展方法WaitForShutdownAsync
), 從字面意思可以看出這個方法會等待知道程序結(jié)束。
/// <summary>
/// Returns a Task that completes when shutdown is triggered via the given token.
/// </summary>
/// <param name="host">The running <see cref="IHost"/>.</param>
/// <param name="token">The token to trigger shutdown.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)
{
//從依賴注入容器中得到程序生命周期對象
var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();
//注冊一個委托到這個token,當(dāng)token 被cancel的時候執(zhí)行委托。
token.Register(state =>
{
((IHostApplicationLifetime)state).StopApplication();
},
applicationLifetime);
var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
//注冊一個委托到ApplicationStopping cancellation token
applicationLifetime.ApplicationStopping.Register(obj =>
{
var tcs = (TaskCompletionSource<object>)obj;
//當(dāng)application stopping事件被觸發(fā)的時候,把waitForStop的結(jié)果設(shè)為null, 任務(wù)執(zhí)行完畢
tcs.TrySetResult(null);
}, waitForStop);
//這個task會一直在等待知道ApplicationStopping 被觸發(fā)
await waitForStop.Task;
// Host will use its default ShutdownTimeout if none is specified.
//關(guān)閉主機(jī)
await host.StopAsync();
}
下面講講主機(jī)的啟動,代碼邏輯是在Host.StartAsync 中
下圖展示了具體流程

從圖中我們可以看到首先會執(zhí)行IHostLifetime實(shí)例的WaitForStartSync方法,IHostLifetime的具體實(shí)現(xiàn)控制著程序什么時候啟動以及什么時候停止,在asp.net core 程序中IHostLifetime的默認(rèn)實(shí)現(xiàn)是Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
下面是WaitForStartAsync方法在ConsoleLifetime中的實(shí)現(xiàn)
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
if (!Options.SuppressStatusMessages)
{
//注冊委托,程序啟動后會調(diào)用OnApplicationStarted
_applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
{
((ConsoleLifetime)state).OnApplicationStarted();
},
this);
//程序在停止時會調(diào)用OnApplicationStopping
_applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
{
((ConsoleLifetime)state).OnApplicationStopping();
},
this);
}
//程序進(jìn)程結(jié)束是會觸發(fā)ProcessExit事件
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
//Ctrl+C會觸發(fā)此事件,即要停止程序
Console.CancelKeyPress += OnCancelKeyPress;
// Console applications start immediately.
return Task.CompletedTask;
}
private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
e.Cancel = true;
ApplicationLifetime.StopApplication();
}
繼續(xù)Host.StartAsync 中的其它邏輯,接下來就是加載所有的IHostService的實(shí)例然后調(diào)用每個實(shí)例的StartAsync方法,上一篇 文章中我們講了程序會配置一個 GenericHostService,這是IHostService的一個實(shí)現(xiàn)類,它的StartAsync中會啟動 Kestrel Web服務(wù),從而能處理request請求。
IHostService啟動后接著會調(diào)用IHostApplicationLifetime.NotifyStarted() 用來觸發(fā) ApplicationStarted 事件。
至此,程序已經(jīng)啟動起來了,Kestrel 會處理Request請求,WaitForShutdownAsync會等待ApplicationStopping 事件執(zhí)行用來關(guān)閉程序。
程序停止流程
在前面描述ConsoleLifetime的時候我們講到了按下Ctrl+C會停止應(yīng)用程序,下面來看下程序是怎么關(guān)閉的。當(dāng)按下Ctrl+C后,程序會調(diào)用ApplicationLifetime.StopApplication方法,在這個方法中會調(diào)用CancellationTokenSource 的 Cancel 方法, Cancel方法會調(diào)用所有在這個token里注冊了的回調(diào)函數(shù),如果你還記得前面我們講程序啟動的時候,在WaitForShutdownAsync 中會在這個token里注冊一個回調(diào)函數(shù)然后等待,那么此時就觸發(fā)了這個會調(diào)用函數(shù)然后完成waitForStop的任務(wù),繼而程序進(jìn)入下一步 host.StopAsync()
// Run the cancellation token callbacks
cancel.Cancel(throwOnFirstException: false);
Host.StopAsync() 是 Host.StartAsync() 的反向操作,首先 調(diào)用了IHostApplicationLIfetime.StopApplication, 之前我們說過按下Ctrl+C后會調(diào)用這個方法,這里又調(diào)用了一次,如果是第二次調(diào)用,程序會什么都不做,這里是為了不適用Ctrl+C方式停止的程序。
接下來循環(huán)停止IHostedService,把之前在StartAsync中啟動的service,在按照倒序方式停止,例如GenericWebHostedService是最后啟動了,在這里要首先停止。
下一步就是調(diào)用IHostApplicationLIfetime.NotifyStopped, 用來觸發(fā)ApplicationStarted 事件 以及注冊的所有回調(diào)函數(shù)。
最好,程序結(jié)束推出Main方法。
總結(jié)
本文比較籠統(tǒng)的介紹了build主機(jī)以及start主機(jī)的過程,要想熟悉這個流程還得自己去讀源碼,或者Debug源碼,這樣可以一步一步的看看程序運(yùn)行的細(xì)節(jié)。
參考資料:
.NET Runtime 代碼庫: https://github.com/dotnet/runtime
ASP.NET CORE 代碼庫:https://github.com/dotnet/aspnetcore
Andrew Lock 的系列文章: Exploring ASP.NET Core 3.0 (andrewlock.net)