美文网首页
ASP.NET Core 5.0 (2) - 模板项目之buil

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

作者: 心有灵 | 来源:发表于2021-11-04 22:21 被阅读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 方法里注册了几个重要的服务

    • IHostApplicationLifetime
    • IHostLifetime
    • IHostEnviornment
      这些服务会在启动主机即调用Run的时候用到。

    启动(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)

    相关文章

      网友评论

          本文标题:ASP.NET Core 5.0 (2) - 模板项目之buil

          本文链接:https://www.haomeiwen.com/subject/yqnaoltx.html