ASP.NET Core 5.0 (2) - 模板项目之buil

2021-11-04  本文已影响0人  心有灵

上一篇文章中介绍了CreateHostBuilder 这个方法的作用以及内部实现,今天这篇文章我们接着讲一下代码中的下一步执行过程,即对IHostBuilder的实例调用Build方法。

Build主机

当程序对主机设置完成后,下一步就是Build主机,Build的实现在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类声明一些委托集合,在配置主机的过成功用户代码和ASP.NET Core源码都添加了一些委托在这个集合中,BuildHostConfiguration 及BuildAppConfiguration 就是循环对应的委托结合并执行委托方法,在CreateServiceProvider 里把一些服务(Service)添加到ServiceCollection里,然后创建.NET内置的服务容器(service container),然后在class的构造函数里就可以得到这些服务的实例,这就是.NET 内置的依赖注入(DI),CreateServiceProvider 方法里注册了几个重要的服务

启动(Run)主机

在配置完主机后就可以调用Run方法启动主机服务了,Run 方法是IHost的一个扩展方法, 它调用RunAsync方法启动主机并阻止调用线程直到方法结束,方法结束的时候应用程序也就结束了。RunAsync里有两行主要的代码

       await host.StartAsync(token).ConfigureAwait(false);
       await host.WaitForShutdownAsync(token).ConfigureAwait(false);

StartAsync()做了很多事,其中最重要的就是启动IHostService,然后程序执行完后会快速的返回,接着调用另一个扩展方法WaitForShutdownAsync
), 从字面意思可以看出这个方法会等待知道程序结束。

/// <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,当token 被cancel的时候执行委托。
            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;
              //当application stopping事件被触发的时候,把waitForStop的结果设为null, 任务执行完毕
                tcs.TrySetResult(null);
            }, waitForStop);
            //这个task会一直在等待知道ApplicationStopping 被触发
            await waitForStop.Task;

            // Host will use its default ShutdownTimeout if none is specified.
           //关闭主机
            await host.StopAsync();
        }

下面讲讲主机的启动,代码逻辑是在Host.StartAsync
下图展示了具体流程

Host启动流程图
从图中我们可以看到首先会执行IHostLifetime实例的WaitForStartSync方法,IHostLifetime的具体实现控制着程序什么时候启动以及什么时候停止,在asp.net core 程序中IHostLifetime的默认实现是Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
下面是WaitForStartAsync方法在ConsoleLifetime中的实现
        public Task WaitForStartAsync(CancellationToken cancellationToken)
        {
            if (!Options.SuppressStatusMessages)
            {
                //注册委托,程序启动后会调用OnApplicationStarted
                _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
                {
                    ((ConsoleLifetime)state).OnApplicationStarted();
                },
                this);
                //程序在停止时会调用OnApplicationStopping
                _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
                {
                    ((ConsoleLifetime)state).OnApplicationStopping();
                },
                this);
            }
            //程序进程结束是会触发ProcessExit事件
            AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
           //Ctrl+C会触发此事件,即要停止程序
            Console.CancelKeyPress += OnCancelKeyPress;

            // Console applications start immediately.
            return Task.CompletedTask;
        }

        private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            e.Cancel = true;
            ApplicationLifetime.StopApplication();
        }

继续Host.StartAsync 中的其它逻辑,接下来就是加载所有的IHostService的实例然后调用每个实例的StartAsync方法,上一篇 文章中我们讲了程序会配置一个 GenericHostService,这是IHostService的一个实现类,它的StartAsync中会启动 Kestrel Web服务,从而能处理request请求。
IHostService启动后接着会调用IHostApplicationLifetime.NotifyStarted() 用来触发 ApplicationStarted 事件。
至此,程序已经启动起来了,Kestrel 会处理Request请求,WaitForShutdownAsync会等待ApplicationStopping 事件执行用来关闭程序。

程序停止流程

在前面描述ConsoleLifetime的时候我们讲到了按下Ctrl+C会停止应用程序,下面来看下程序是怎么关闭的。当按下Ctrl+C后,程序会调用ApplicationLifetime.StopApplication方法,在这个方法中会调用CancellationTokenSource 的 Cancel 方法, Cancel方法会调用所有在这个token里注册了的回调函数,如果你还记得前面我们讲程序启动的时候,在WaitForShutdownAsync 中会在这个token里注册一个回调函数然后等待,那么此时就触发了这个会调用函数然后完成waitForStop的任务,继而程序进入下一步 host.StopAsync()

            // Run the cancellation token callbacks
            cancel.Cancel(throwOnFirstException: false);

Host.StopAsync() 是 Host.StartAsync() 的反向操作,首先 调用了IHostApplicationLIfetime.StopApplication, 之前我们说过按下Ctrl+C后会调用这个方法,这里又调用了一次,如果是第二次调用,程序会什么都不做,这里是为了不适用Ctrl+C方式停止的程序。

接下来循环停止IHostedService,把之前在StartAsync中启动的service,在按照倒序方式停止,例如GenericWebHostedService是最后启动了,在这里要首先停止。

下一步就是调用IHostApplicationLIfetime.NotifyStopped, 用来触发ApplicationStarted 事件 以及注册的所有回调函数。

最好,程序结束推出Main方法。

总结

本文比较笼统的介绍了build主机以及start主机的过程,要想熟悉这个流程还得自己去读源码,或者Debug源码,这样可以一步一步的看看程序运行的细节。

参考资料:
.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)

上一篇下一篇

猜你喜欢

热点阅读