如何远程关闭一个ASP.NET Core应用?
在《历数依赖注入的N种玩法》演示系统自动注册服务的实例中,我们会发现输出的列表包含两个特殊的服务,它们的对应的服务接口分别是IApplicationLifetime和IHostingEnvironment,我们将分别实现这两个接口的服务统称在ApplicationLifetime和HostingEnvironment。我们从其命名即可以看出ApplicationLifetime与应用的声明周期有关,而HostingEnvironment则用来表示当前的执行环境,本篇文章我们着重来了解ApplicationLifetime与整个AASP.NET Core应用的生命周期有何关系。[本文已经同步到《ASP.NET Core框架揭秘》之中]
目录
一、ApplicationLifetime
二、WebHost的Run方法
三、远程关闭应用
一、ApplicationLifetime
从命名的角度来看,ApplicationLifetime貌似是对当前应用生命周期的描述,而实际上它存在的目的仅仅是在应用启动和关闭时对相关组件发送相应的信号或者通知而已。如下面的代码片段所示,IApplicationLifetime接口具有三个CancellationToken类型的属性(ApplicationStarted、ApplicationStopping和ApplicationStopped),如果需要在应用自动和终止前后执行某种操作,我们可以注册相应的回调在这三个CancellationToken对象上。除了这三个类型为CancellationToken的属性,IApplicationLifetime接口还定义了一个StopApplication方法,我们可以调用这个方法发送关闭应用的信号,并最终真正地关闭应用。
1: public interface IApplicationLifetime
2: {
3: CancellationToken ApplicationStarted { get; }
4: CancellationToken ApplicationStopping { get; }
5: CancellationToken ApplicationStopped { get; }
6:
7: void StopApplication();
8: }
ASP.NET Core默认使用的ApplicationLifetime是具有如下定义的一个同名类型。可以看出它实现的三个属性返回的CancellationToken对象是通过三个对应的CancellationTokenSource生成。除了实现IApplicationLifetime接口的StopApplication方法用于发送“正在关闭”通知之外,这个类型还定义了额外两个方法(NotifyStarted和NotifyStopped)用于发送“已经开启/关闭”的通知。
1: public class ApplicationLifetime : IApplicationLifetime
2: {
3: private readonly CancellationTokenSource _startedSource = new CancellationTokenSource();
4: private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource();
5: private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource();
6:
7: public CancellationToken ApplicationStarted
8: {
9: get { return _startedSource.Token; }
10: }
11: public CancellationToken ApplicationStopped
12: {
13: get { return _stoppedSource.Token; }
14: }
15: public CancellationToken ApplicationStopping
16: {
17: get { return _stoppingSource.Token; }
18: }
19:
20: public void NotifyStarted()
21: {
22: _startedSource.Cancel(false);
23: }
24: public void NotifyStopped()
25: {
26: _stoppedSource.Cancel(false);
27: }
28: public void StopApplication()
29: {
30: _stoppingSource.Cancel(false);
31: }
32: }
当WebHost因Start方法的执行而被开启的时候,它最终会调用ApplicationLifetime的NotifyStarted方法对外发送应用被成功启动的信号。不知道读者朋友们又被注意到,WebHost仅仅定义了启动应用的Start方法,并不曾定义终止应用的Stop或者Close方法,它仅仅在Dispose方法中调用了ApplicationLifetime的StopApplication方法。
1: public class WebHost : IWebHost
2: {
3: private ApplicationLifetime _applicationLifetime;
4: public IServiceProvider Services { get;}
5:
6: public void Start()
7: {
8: ...
9: _applicationLifetime.NotifyStarted();
10: }
11:
12: public void Dispose()
13: {
14: _applicationLifetime.StopApplication();
15: (this.Services as IDisposable)?.Dispose();
16: _applicationLifetime.NotifyStopped();
17: }
18: ...
19: }
二、WebHost的Run方法
我们知道启动应用最终是通过调用作为宿主的WebHost的Start方法来完成的,但是我们之前演示的所有实例都不曾显式地调用过这个方法,我们调用的是它的扩展方法Run。毫无疑问,WebHost的Run方法肯定会调用Start方法来开启WebHost,但是除此之外,这个Run方法还有何特别之处呢?
Run方法的目的除了启动WebHost之外,它实际上会阻塞当前进程直到应用关闭。我们知道应用的关闭的意图是通过利用ApplicationLifetime发送相应信号的方式实现的,所以这个Run方法在启动WebHost的时候,会以阻塞当前线程的方式等待直至接收到这个信号。如下所示的代码片段基本上体现了这两个扩展方法Run的实现逻辑。
1: public static class WebHostExtensions
2: {
3: public static void Run(this IWebHost host)
4: {
5: using (CancellationTokenSource cts = new CancellationTokenSource())
6: {
7: //Ctrl+C: 关闭应用
8: Console.CancelKeyPress += (sender, args) =>
9: {
10: cts.Cancel();
11: args.Cancel = true;
12: };
13: host.Run(cts.Token);
14: }
15: }
16:
17: public static void Run(this IWebHost host, CancellationToken token)
18: {
19: using (host)
20: {
21: //显示应用基本信息
22: host.Start();
23: IApplicationLifetime applicationLifetime = host.Services.GetService<IApplicationLifetime>();
24: token.Register(state => ((IApplicationLifetime)state).StopApplication(), applicationLifetime);
25: applicationLifetime.ApplicationStopping.WaitHandle.WaitOne();
26: }
27: }
28: }
上面这个代码片段还体现了另一个细节。虽然WebHost实现了IDisposable接口,原则上我们需要在关闭的时候显式地调用其Dispose方法。针对这个方法的调用非常重要,因为它的ServiceProvider只能在这个方法被调用时才能被回收释放。但是之前所有演示的实例都没有这么做,因为Run方法会自动帮助回收释放掉指定的这个WebHost。
三、远程关闭应用
既然WebHost在启动之后会利用ApplicationLifetime等待Stopping信号的发送,这就意味着组成ASP.NET Core管道的服务器和任何一个中间件都可以在适当的时候调用ApplicationLifetime的StopApplication来关闭应用。对于《服务器在管道中的“龙头”地位》介绍的KestrelServer,我们知道在构造这个对象的时候必须指定一个ApplicationLifetime对象,其根本的目的在于当发送某些无法恢复的错误时,它可以利用这个对象关闭应用。
接下来我们通过实例的方式来演示如何在一个中间件中利用这个ApplicationLifetime对象实现对应用的远程关闭,为此我们将这个中间件命名为RemoteStopMiddleware。RemoteStopMiddleware实现远程关闭应用的原理很简单,我们远程发送一个Head请求,并且在该请求中添加一个名为“Stop-Application”的报头传到希望关闭应用的意图,该中间件接收到这个请求之后会关闭应用,而响应中会添加一个“Application-Stopped”报头表明应用已经被关闭。
1: public class RemoteStopMiddleware
2: {
3: private RequestDelegate _next;
4: private const string RequestHeader = "Stop-Application";
5: private const string ResponseHeader = "Application-Stopped";
6:
7: public RemoteStopMiddleware(RequestDelegate next)
8: {
9: _next = next;
10: }
11:
12: public async Task Invoke(HttpContext context, IApplicationLifetime lifetime)
13: {
14: if (context.Request.Method == "HEAD" && context.Request.Headers[RequestHeader].FirstOrDefault() == "Yes")
15: {
16: context.Response.Headers.Add(ResponseHeader, "Yes");
17: lifetime.StopApplication();
18: }
19: else
20: {
21: await _next(context);
22: }
23: }
24: }
如上所示的代码片段是RemoteStopMiddleware这个中间件的完整定义,实现逻辑很简单,完全没有必要再赘言解释。我们在一个控制台应用中采用如下的程序启动一个Hello World应用,并注册此RemoteStopMiddleware中间件。在启动这个应用之后,我们借助Fiddler发送向目标地址发送三次请求,其中第一次和第三次普通的GET请求,而第二次则是为了远程关闭应用的HEAD请求。如下所示的是三次请求与响应的内容,由于应用被第二次请求关闭,所以第三次请求会返回一个状态码为502的响应。
1: //第1次请求与响应
2: GET http://localhost:5000/ HTTP/1.1
3: User-Agent: Fiddler
4: Host: localhost:5000
5:
6: HTTP/1.1 200 OK
7: Date: Sun, 06 Nov 2016 06:15:03 GMT
8: Transfer-Encoding: chunked
9: Server: Kestrel
10:
11: Hello world!
12:
13: //第2次请求与响应
14: HEAD http://localhost:5000/ HTTP/1.1
15: Stop-Application: Yes
16: User-Agent: Fiddler
17: Host: localhost:5000
18:
19: HTTP/1.1 200 OK
20: Date: Sun, 06 Nov 2016 06:15:34 GMT
21: Server: Kestrel
22: Application-Stopped: Yes
23:
24: //第3次请求与响应
25: GET http://localhost:5000/ HTTP/1.1
26: User-Agent: Fiddler
27: Host: localhost:5000
28:
29: HTTP/1.1 502 Fiddler - Connection Failed
30: Date: Sun, 06 Nov 2016 06:15:44 GMT
31: Content-Type: text/html; charset=UTF-8
32: Connection: close
33: Cache-Control: no-cache, must-revalidate
34: Timestamp: 14:15:44.790
35:
36: [Fiddler] The connection to 'localhost' failed...
如何远程关闭一个ASP.NET Core应用?的更多相关文章
- Kubernetes初探[1]:部署你的第一个ASP.NET Core应用到k8s集群
Kubernetes简介 Kubernetes是Google基于Borg开源的容器编排调度引擎,作为CNCF(Cloud Native Computing Foundation)最重要的组件之一,它的 ...
- 如何一秒钟从头构建一个 ASP.NET Core 中间件
前言 其实地上本没有路,走的人多了,也便成了路. -- 鲁迅 就像上面鲁迅说的那样,其实在我们开发中间件的过程中,微软并没有制定一些策略或者文档来约束你如何编写一个中间件程序, 但是其中却存在者一些最 ...
- 使用Visual Studio Code创建第一个ASP.NET Core应用程序
全文翻译自:Your First ASP.NET Core Application on a Mac Using Visual Studio Code 这篇文章将向你展示如何在Mac上写出你的第一个A ...
- 从零写一个Asp.net core手脚架(模型验证)
一个asp.net core项目,一定包含了各种的实体,在RESTful api里面,有很多的参数传递,不建立实体则大量的参数需要自定验证正确性,并且Action上面会写的密密麻麻的参数 在asp.n ...
- 【翻译】在Mac上使用VSCode创建你的第一个Asp.Net Core应用
Setting Up Your Development Environment 设置你的开发环境 To setup your development machine download and inst ...
- .NET Core RC2发布在即,我们试着用记事本编写一个ASP.NET Core RC2 MVC程序
在.NET Core 1.0.0 RC2即将正式发布之际,我也应应景,针对RC2 Preview版本编写一个史上最简单的MVC应用.由于VS 2015目前尚不支持,VS Code的智能感知尚欠火候,所 ...
- 用.Net Core控制台模拟一个ASP.Net Core的管道模型
在我的上几篇文章中降到了asp.net core的管道模型,为了更清楚地理解asp.net core的管道,再网上学习了.Net Core控制台应用程序对其的模拟,以加深映像,同时,供大家学习参考. ...
- 用VSCode开发一个asp.net core 2.0+angular 5项目(4): Angular5全局错误处理
第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 第二部分: http://www.cnblogs.com/cgzl/p/8481825.html 第三 ...
- Kubernetes中分布式存储Rook-Ceph的使用:一个ASP.NET Core MVC的案例
在<Kubernetes中分布式存储Rook-Ceph部署快速演练>文章中,我快速介绍了Kubernetes中分布式存储Rook-Ceph的部署过程,这里介绍如何在部署于Kubernete ...
随机推荐
- 前端网络、JavaScript优化以及开发小技巧
一.网络优化 YSlow有23条规则,中文可以参考这里.这几十条规则最主要是在做消除或减少不必要的网络延迟,将需要传输的数据压缩至最少. 1)合并压缩CSS.JavaScript.图片,静态资源CDN ...
- 一步步开发自己的博客 .NET版(11、Web.config文件的读取和修改)
Web.config的读取 对于Web.config的读取大家都很属性了.平时我们用得比较多的就是appSettings节点下配置.如: 我们对应的代码是: = ConfigurationManage ...
- Axure 8.0.0.3312可用注册码
用户名:aaa 注册码:2GQrt5XHYY7SBK/4b22Gm4Dh8alaR0/0k3gEN5h7FkVPIn8oG3uphlOeytIajxGU 用户名:axureuser 序列号:8wFfI ...
- log4net使用手册
1. log4net简介 log4net是.Net下一个非常优秀的开源日志记录组件.log4net记录日志的功能非常强大.它可以将日志分不同的等级,以不同的格式,输出到不同的媒介.Java平台下,它还 ...
- 超详细mysql left join,right join,inner join用法分析
下面是例子分析表A记录如下: aID aNum 1 a20050111 2 a20050112 3 a20050113 4 ...
- Photoshop、Illustrator思维导图笔记
半年前学习Photoshop时记得的思维导图笔记,可能不是很全,常用的基本都记下了.
- 凭吊一下ASP.NET 5,然后跨平台,越跨越开心
ASP.NET 5 is dead ASP.NET 5在今年早些时候被宣判死刑了.但是这并不影响我们之前在ASP.NET 5乃至ASP.NET MVC平台上的经验累积--没错,微软改名部门又立功了!他 ...
- Storm构建分布式实时处理应用初探
最近利用闲暇时间,又重新研读了一下Storm.认真对比了一下Hadoop,前者更擅长的是,实时流式数据处理,后者更擅长的是基于HDFS,通过MapReduce方式的离线数据分析计算.对于Hadoop, ...
- Redis集群案例与场景分析
1.背景 Redis的出现确实大大地提高系统大并发能力支撑的可能性,转眼间Redis的最新版本已经是3.X版本了,但我们的系统依然继续跑着2.8,并很好地支撑着我们当前每天5亿访问量的应用系统.想当年 ...
- With(ReadPast)就不会被阻塞吗?
在生产环境中,会有很多使用ReadPast查询提示的场合,来避免正在被其它事务锁定的行对当前查询造成阻塞,而又不会获取到“脏数据”. 可是很多人都疑惑,为什么我使用了ReadPast仍然有时会被阻塞? ...