在《历数依赖注入的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应用?的更多相关文章

  1. Kubernetes初探[1]:部署你的第一个ASP.NET Core应用到k8s集群

    Kubernetes简介 Kubernetes是Google基于Borg开源的容器编排调度引擎,作为CNCF(Cloud Native Computing Foundation)最重要的组件之一,它的 ...

  2. 如何一秒钟从头构建一个 ASP.NET Core 中间件

    前言 其实地上本没有路,走的人多了,也便成了路. -- 鲁迅 就像上面鲁迅说的那样,其实在我们开发中间件的过程中,微软并没有制定一些策略或者文档来约束你如何编写一个中间件程序, 但是其中却存在者一些最 ...

  3. 使用Visual Studio Code创建第一个ASP.NET Core应用程序

    全文翻译自:Your First ASP.NET Core Application on a Mac Using Visual Studio Code 这篇文章将向你展示如何在Mac上写出你的第一个A ...

  4. 从零写一个Asp.net core手脚架(模型验证)

    一个asp.net core项目,一定包含了各种的实体,在RESTful api里面,有很多的参数传递,不建立实体则大量的参数需要自定验证正确性,并且Action上面会写的密密麻麻的参数 在asp.n ...

  5. 【翻译】在Mac上使用VSCode创建你的第一个Asp.Net Core应用

    Setting Up Your Development Environment 设置你的开发环境 To setup your development machine download and inst ...

  6. .NET Core RC2发布在即,我们试着用记事本编写一个ASP.NET Core RC2 MVC程序

    在.NET Core 1.0.0 RC2即将正式发布之际,我也应应景,针对RC2 Preview版本编写一个史上最简单的MVC应用.由于VS 2015目前尚不支持,VS Code的智能感知尚欠火候,所 ...

  7. 用.Net Core控制台模拟一个ASP.Net Core的管道模型

    在我的上几篇文章中降到了asp.net core的管道模型,为了更清楚地理解asp.net core的管道,再网上学习了.Net Core控制台应用程序对其的模拟,以加深映像,同时,供大家学习参考. ...

  8. 用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 第三 ...

  9. Kubernetes中分布式存储Rook-Ceph的使用:一个ASP.NET Core MVC的案例

    在<Kubernetes中分布式存储Rook-Ceph部署快速演练>文章中,我快速介绍了Kubernetes中分布式存储Rook-Ceph的部署过程,这里介绍如何在部署于Kubernete ...

随机推荐

  1. [BOT] 一种android中实现“圆角矩形”的方法

    内容简介 文章介绍ImageView(方法也可以应用到其它View)圆角矩形(包括圆形)的一种实现方式,四个角可以分别指定为圆角.思路是利用"Xfermode + Path"来进行 ...

  2. 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文

    阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...

  3. 干货分享:SQLSERVER使用裸设备

    干货分享:SQLSERVER使用裸设备 这篇文章也适合ORACLE DBA和MYSQL DBA 阅读 裸设备适用于Linux和Windows 在ORACLE和MYSQL里也是支持裸设备的!! 介绍 大 ...

  4. InstallShield 脚本语言学习笔记

    InstallShield脚本语言是类似C语言,利用InstallShield的向导或模板都可以生成基本的脚本程序框架,可以在此基础上按自己的意愿进行修改和添加.     一.基本语法规则      ...

  5. 使用ubuntu作为web开发环境的一些感受

    从ms-dos,win95,win98,winMe,winXp,vista,win7,win10我都有使用的经历,我使用时间最长的应属winxp,其次是win7,说实话,我觉得这两个系统是微软做的最好 ...

  6. 前端HTML5几种存储方式的总结

    接下来要好好总结一些知识,秋招来啦...虽然有好多知识都不大会,但是还是要努力一下,运气这种东西,谁知道呢~ 总体情况 h5之前,存储主要是用cookies.cookies缺点有在请求头上带着数据,大 ...

  7. Loadrunner Http Json接口压力测试

    前天接到了一个测试任务,要求测试一下ES(elsticsearch)在不同并发下的查询效率.如图: 业务场景是在客户端根据具体车牌查询相关车辆信息,结果返回前10条记录. 从图中可以看到,接口的请求参 ...

  8. Asp.NET + SQLServer 部署注意事项

    1. 内存设置最大值(如果不设置, 会造成内存占用太大,带来性能问题) IIS 设置最大内存 sqlserver 设置最大内存

  9. redis大幅性能提升之使用管道(PipeLine)和批量(Batch)操作

    前段时间在做用户画像的时候,遇到了这样的一个问题,记录某一个商品的用户购买群,刚好这种需求就可以用到Redis中的Set,key作为productID,value 就是具体的customerid集合, ...

  10. 【swift】BlockOperation和GCD实用代码块

    //BlockOperation // // ViewController.swift import UIKit class ViewController: UIViewController { @I ...