Asp.net Core 主机生命周期的管理
1. 回顾CancellationToken
CancellationToken类有个容易被忽视的功能,那就是它包含一个Register()方法,这个方法可以注册一个委托,当这个CancellationToken类对象被Cancel时可以触发这个委托的执行。Register()可以被执行多次,表示注册了好几个委托,Cancel到来时,可以触发执行所有这些已注册的委托。
Asp.net Core的非泛型主机运用了这个原理进行生命周期管理。
2. 生命周期管理
生命周期管理指代码框架可以管理主机启动和停止(甚至停止中)的过程。用户可以很方便的嵌入自己的代码,对这些生命过程进行监控,比如我们想在启动时输出点信息。
3. 泛型主机与应用类主机服务
Asp.net Core将主机分为泛型主机(Host)和应用主机服务(Application Host,我们也称非泛型主机),Web主机就是一个应用主机,应用主机也称为主机服务(必需实现IHostedService),总之:
- 泛型主机Host,它实现的是IHost接口,它好比是黑社会老大,其他的应用主机都归它管,它是全场唯一的。
- 非泛型主机,即应用类主机服务,它必须实现IHostedService接口,它可以是多个的,它是由泛型主机Host启动的。最重要的Web 主机,其实就是非泛型主机之一。我们可以往泛型主机里注入自己想要注入的其他应用主机。asp.net core 3.1利用IHostedService为系统注入自己的主机
下面解密下Host公开的StartAsync()方法内的源码:
public async Task StartAsync(CancellationToken cancellationToken = default){_logger.Starting();await _hostLifetime.WaitForStartAsync(cancellationToken);// 默认_hostLifetime就是ConsoleLifetime对象cancellationToken.ThrowIfCancellationRequested();_hostedServices = Services.GetService<IEnumerable<IHostedService>>();foreach (var hostedService in _hostedServices){// Fire IHostedService.Startawait hostedService.StartAsync(cancellationToken).ConfigureAwait(false);}// Fire IApplicationLifetime.Started_applicationLifetime?.NotifyStarted();// _applicationLifetime是ApplicationLifetime对象。_logger.Started();}
看这里的这个foreach语句,就是将hostedService里头的IHostedService主机服务挨个启动。hostedService则是通过DI(依赖注入)注入的。
4. IHostLifetime
Asp.net Core会注入一个IHostLifetime的类对象,并且这个对象是Singletone的,即它是全场唯一的。它是用来掌管泛型Host主机的生命周期的。除非我们想自己实现一个,否则我们可以不需要建这么个类对象,它默认注入的是ConsoleLifetime类对象,该类实现了IHostLifetime接口。
看一下内部代码ConsoleLifetime.cs片段(WaitForStartAsync()方法是IHostLifetime接口的方法):
public Task WaitForStartAsync(CancellationToken cancellationToken){if (!Options.SuppressStatusMessages){ApplicationLifetime.ApplicationStarted.Register(() =>{Console.WriteLine("Application started. Press Ctrl+C to shut down.");Console.WriteLine($"Hosting environment: {Environment.EnvironmentName}");Console.WriteLine($"Content root path: {Environment.ContentRootPath}");});}AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) =>{ApplicationLifetime.StopApplication();_shutdownBlock.WaitOne();};Console.CancelKeyPress += (sender, e) =>{e.Cancel = true;ApplicationLifetime.StopApplication();};// Console applications start immediately.return Task.CompletedTask;}
这里的第13-22行,是用来捕获程序退出事件的,包括在控制台下按了Ctrl+C,或者点击了控制台对话框右上角的×。注意,这里(15行,21行)退出的时候触发了ApplicationLifetime.StopApplication()的执行。这个方法则是被Host的StartAsync()方法所调用。
后面我们会看到ApplicationLifetime.StopApplication()的执行就会触发一系列CancellationToken注册的委托的执行,而这里的ApplicationLifetime则代表了非泛型主机(应用主机服务)的生命管理对象。
5. IHostApplicationLifetime与三个生命周期类型的委托
IHostApplicationLifetime原来叫IApplicationLifetime,微软将这个东西改名了,说实话,改名后的源码未找到,也不知道这是为什么。我们找不到IHostApplicationLifetime,但我们可以找到IApplicationLifetime,它包含一个叫做StopApplication()方法。我们先理解它就可以了。这接口它也是被依赖注入的,它是用来掌管其他非泛型主机生命周期的,并且它也是Singleton,即它也是独一份的,Asp.net Core框架默认注入的是ApplicationLifetime这个类对象。那么问题来了,既然我们可以注入无数个不同的非泛型主机,按道理一个非泛型主机包含一个生命管理周期对象,怎么就只有一个生命周期管理对象?这里的原因是,Asp.net Core将非泛型主机和IHostApplicationLifetime是分为两个独立的类,然后用一个IHostApplicationLifetime对象的注册机制(也就是刚开始谈到的CancellationToken的Register()方法)来掌管所有非泛型主机的生命周期。ApplicationLifetime类对象里包含三个CancellationToken类对象:
- ApplicationStarted,它可以触发Started委托,即非泛型主机启动完毕的委托;
- ApplicationStopping,它可以触发Stopping委托,即非泛型主机开始停止的委托;
- ApplicationStopped,它可以触发Stopped委托,即非泛型主机停止完毕后的委托;
从这三个名字就可以知道分别是用来掌管启动完毕、停止中、停止完毕后三个生命周期。分别对这三个令牌执行Cancel取消来触发各自注册了的委托。所以说,这里Cancel根本不是本来的取消意思,仅仅是Asp.net Core的技术团队使用了CancellationToken类的这个特性而已,我自己想想感觉这个用法是巧妙,但是总感觉有点蛋疼。从这里,我们就可以推断出,如果自己注册的非泛型主机,想使用生命周期管理,那就可以在非泛型主机的构造函数中携带入这个IHostApplicationLifetime,然后我们只要在这里头为上面的三个令牌Regiseter()一下自己想要的委托。
现在我们看一下ApplicationLifetime的StopApplication()方法,这个方法是IHostApplicationLifetime的接口方法。这个方法里面实际上就是调用了令牌的Cancel()方法,触发一连串的Regiseter()委托的执行。
那么Asp.net Core框架又是在哪里触发StopApplication()方法的呢?我们再回顾IHostLifetime一节提到的代码片段的第13-22行,这里在捕获到程序退出时会触发ApplicationLifetime的StopApplication()方法。由此触发Stopping的生命过程比较清晰了。
6. 已启动与已停止的生命过程
ApplicationLifetime的StopApplication()方法可以触发Stopping委托。Started委托和Stopped的委托则是靠调用ApplicationLifetime的NotifyStarted()和NotifyStopped()方法达成的。NotifyStarted()在Host的StartAsync()方法中被调用。NotifyStopped()在Host的StopAsync()方法中被调用。NotifyStarted()方法前面已经贴过了,现在贴出Host的StopAsync()方法:
public async Task StopAsync(CancellationToken cancellationToken = default){_logger.Stopping();using (var cts = new CancellationTokenSource(_options.ShutdownTimeout))using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken)){var token = linkedCts.Token;// Trigger IApplicationLifetime.ApplicationStopping_applicationLifetime?.StopApplication();IList<Exception> exceptions = new List<Exception>();if (_hostedServices != null) // Started?{foreach (var hostedService in _hostedServices.Reverse()){token.ThrowIfCancellationRequested();try{await hostedService.StopAsync(token).ConfigureAwait(false);}catch (Exception ex){exceptions.Add(ex);}}}token.ThrowIfCancellationRequested();await _hostLifetime.StopAsync(token);// Fire IApplicationLifetime.Stopped_applicationLifetime?.NotifyStopped();// 这里通知已停止委托的执行if (exceptions.Count > 0){var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);_logger.StoppedWithException(ex);throw ex;}}_logger.Stopped();}
现在新的问题来了,哪里调用了Host的StartAsync()方法和StopAsync()方法?这个答案在HostingAbstractionsHostExtensions.cs源码当中可以清晰的找到,贴出它的整个源码:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;namespace Microsoft.Extensions.Hosting
{public static class HostingAbstractionsHostExtensions{/// <summary>/// Starts the host synchronously./// </summary>/// <param name="host"></param>public static void Start(this IHost host){host.StartAsync().GetAwaiter().GetResult();}/// <summary>/// Attempts to gracefully stop the host with the given timeout./// </summary>/// <param name="host"></param>/// <param name="timeout">The timeout for stopping gracefully. Once expired the/// server may terminate any remaining active connections.</param>/// <returns></returns>public static Task StopAsync(this IHost host, TimeSpan timeout){return host.StopAsync(new CancellationTokenSource(timeout).Token);}/// <summary>/// Block the calling thread until shutdown is triggered via Ctrl+C or SIGTERM./// </summary>/// <param name="host">The running <see cref="IHost"/>.</param>public static void WaitForShutdown(this IHost host){host.WaitForShutdownAsync().GetAwaiter().GetResult();}/// <summary>/// Runs an application and block the calling thread until host shutdown./// </summary>/// <param name="host">The <see cref="IHost"/> to run.</param>public static void Run(this IHost host){host.RunAsync().GetAwaiter().GetResult();}/// <summary>/// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered./// </summary>/// <param name="host">The <see cref="IHost"/> to run.</param>/// <param name="token">The token to trigger shutdown.</param>public static async Task RunAsync(this IHost host, CancellationToken token = default){using (host){await host.StartAsync(token);await host.WaitForShutdownAsync(token);}}/// <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>public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default){var applicationLifetime = host.Services.GetService<IApplicationLifetime>();token.Register(state =>{((IApplicationLifetime)state).StopApplication();},applicationLifetime);var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);applicationLifetime.ApplicationStopping.Register(obj =>{var tcs = (TaskCompletionSource<object>)obj;tcs.TrySetResult(null);}, waitForStop);await waitForStop.Task;// Host will use its default ShutdownTimeout if none is specified.await host.StopAsync();}}
}
改源码就是对Host的扩展。这里面有我们熟悉的Asp.net Core模板中的Run()方法,这个方法最后就会调用Host的StartAsync()方法。而Host的扩展方法WaitForShutdownAsync()则会调用StopAsync()。
这里Run()内部通过GetAwaiter().GetResult()可以将系统处于停等状态。它内部又利用了TaskCompletionSource的特性达到停等状态。
7. 评论
应该说利用CancellationToken类的特性来管理生命周期,是一种技巧。这种技巧对于我们外人来说感到生搬硬套。不去了解,确实很难把"取消"和生命管理过程联系在一起,但是微软技术团队就是这么直接的把它们粘在了一起。
IHostApplicationLifetime接口直接暴露有StopApplication()方法,用来触发Stopping委托执行。IHostApplicationLifetime的默认实现有额外的NotifyStarted()和NotifyStopped()方法,用来触发Started和Stopped委托。但是遗憾的是,这两个方法并不在IHostApplicationLifetime接口中,因此Host在使用ApplicationLifetime时需要类型转换(参考下方Host.cs代码的第5行),如果这样的话,是不是意味着用户无法替换IHostApplicationLifetime的实现了?经过试验,真的无法注入自定义的IHostApplicationLifetime,运行时会直接抛出异常。这不能算是一个Bug,感觉像是微软技术团队代码框架设计问题。好在我们一般是不需要自定义一个IHostApplicationLifetime。
public Host(IServiceProvider services, IApplicationLifetime applicationLifetime, ILogger<Host> logger,IHostLifetime hostLifetime, IOptions<HostOptions> options){Services = services ?? throw new ArgumentNullException(nameof(services));_applicationLifetime = (applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime))) as ApplicationLifetime;_logger = logger ?? throw new ArgumentNullException(nameof(logger));_hostLifetime = hostLifetime ?? throw new ArgumentNullException(nameof(hostLifetime));_options = options?.Value ?? throw new ArgumentNullException(nameof(options));}
Asp.net Core 主机生命周期的管理相关推荐
- ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理
在ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发.订阅和处理的流程.这种实现太简单了,百十行 ...
- IIS 5.0 和 6.0 的 ASP.NET 应用程序生命周期
本文内容 应用程序生命周期概述 生命周期事件和 Global.asax 文件 编译生命周期 HTTP 模块 本文概述 VS 2008 ASP.NET 应用程序的生命周期,列出了重要的生命周期事件,并描 ...
- IIS 7.0的ASP.NET应用程序生命周期概述
小结于:http://msdn.microsoft.com/zh-cn/library/bb470252(v=vs.100).aspx IIS 7.0的ASP.NET应用程序生命周期概述 (一)结构概 ...
- ASP.NET服务器控件的生命周期分析
本文实例分析了ASP.NET服务器控件的生命周期.分享给大家供大家参考.具体如下: (1)初始化----在此阶段中,主要完成两项工作:一.初始化在传入Web请求生命周期内所需的设置:二.跟踪视图状态. ...
- IIS 7.0 的 ASP.NET 应用程序生命周期概述
本主题介绍在 IIS 7.0 集成模式下运行以及与 .NET Framework 3.0 或更高版本一起运行的 ASP.NET 应用程序的应用程序生命周期.IIS 7.0 还支持经典模式,其行为类似于 ...
- ASP.NET 应用程序生命周期概述
本主题概述应用程序生命周期,列出重要的生命周期事件,并描述如何编写适合应用程序生命周期的代码.在 ASP.NET 中,若要对 ASP.NET 应用程序进行初始化并使它处理请求,必须执行一些处理步骤.此 ...
- 【转】Asp.net页面的生命周期
介绍 Asp.net是微软.Net战略的一个组成部分.它相对以前的Asp有了很大的发展,引入了许多的新机制.本文就Asp.net页面的生命周期向大家做一个初步的介绍,以期能起到指导大家更好.更灵活地操 ...
- 数据港:攻破OPEX+SLA难题,实现全生命周期效能管理
在互联网高速发展的变革时代,数据中心产业作为重要的信息基础设施也在发生重要的改变,很多企业致力于探寻有效提升数据中心运维及能效管理生命周期的最佳方式,打造核心竞争优势. 提到上海数据港,相信很多人对其 ...
- java调试生命周期,一种基于JAVA的智能合约生命周期的管理方法与流程
本发明涉及区块链技术,尤其涉及一种基于JAVA的智能合约生命周期的管理方法. 背景技术: 区块链技术,区块链是一种新型去中心化协议,能安全地存储数字货币交易或其他数据,信息不可伪造和篡改,区块链上的交 ...
最新文章
- lampp mysql 等待响应时间很长_XAMPP 的 phpMyAdmin 就会有文件大小限制、上传超时等各种问题...
- 2015-12-03 AD中用户属性Lastlogon与LastlogonTimeStamp的区别
- C++ 读入一行字符串
- Apple 的 CEO和Google的CEO在星巴克聊什么呢?
- 笔记05 局部类型
- lua中table函数库
- 如何使用jMeter对某个OData服务进行高并发性能测试
- 随机地图生成工具 fastMapper
- openCv 图像顺时针 逆时针旋转
- 实时视频通话超低延迟架构的思考与实践
- 校企同游快乐工作——湖南工程职院美和易思教师开展素质拓展活动
- ryzen linux 搭配显卡,AMD Ryzen 2600CPU搭配什么显卡比较合理?
- 2020年8月份需求排期
- win10 vs2015 wxWidgets编译
- Java 适配器模式详解
- One-move Checkmate (ZOJ 1598)
- 医院管理信息系统java版本
- 使用vscode编译器:检测到 #include 错误。请更新 includePath。已为此翻译单元,无法打开源文件<iostream>
- matlab在线性代数中的应用开题报告,矩阵应用开题报告.doc
- 苹果备忘录显示无法连接服务器失败,备忘录帮助