ASP.NET Core 5.0 (2) - 模板項(xiàng)目之build and start host

上一篇文章中介紹了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
下圖展示了具體流程

Host啟動流程圖

從圖中我們可以看到首先會執(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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容