在上一篇文章中介绍了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 中
下图展示了具体流程
从图中我们可以看到首先会执行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)
网友评论