在《历数依赖注入的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. Asp.NET MVC 使用 SignalR 实现推送功能二(Hubs 在线聊天室 获取保存用户信息)

    简单介绍 关于SignalR的简单实用 请参考 Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室) 在上一篇中,我们只是介绍了简单的消息推送,今天我们来修改一下,实现 ...

  2. Java编程里类的继承

    今天,我们将要讨论的内容是Java里面类的继承的相关概念. 说到继承,我相信大家都不陌生.生活中,子承父业,子女继承父母的财产,这就是继承.实际上,Java里的继承也是如此.对于一个类来说,它的数据成 ...

  3. GPU keylogger && GPU Based rootkit(Jellyfish rootkit)

    catalog . OpenCL . Linux DMA(Direct Memory Access) . GPU rootkit PoC by Team Jellyfish . GPU keylogg ...

  4. Oracle 修改一行数据内存主要变化

    向Oracle 数据库发出请求,修改一行数据,在内存中主要有以下变化: 1. 服务器进程将包含该行数据的块读取到内存中 2. 写redo日志.将内存中该数据块指向undo表空间中数据块的变更向量(Ch ...

  5. Wireshark网络端点和会话

    如果想让网络进行正常通信,你必须至少拥有两台设备进行数据流交互.端点(endpoint)就是指网络上能够发送和接受数据的一台设备.举例来说,在TCP/IP的通信中就有两个断电:接收和发送数据系统的IP ...

  6. C# out ref 用法总结

    C#里面的 out 和ref参数时常会用到,但对它们的区别比较模糊.所以总结一下.下面是测试代码: public void Start() { //outSum没必要赋值,赋值了也完全没用. //如果 ...

  7. [2017BUAA软工]第0次作业

    第0次作业 Part 1:结缘计算机 1. 你为什么选择计算机专业?你认为你的条件如何?和这些博主比呢? 我跟这篇博客中的作者相似的地方在于,我们都在一个比较早的阶段接触了计算机,我家乡的经济在全国来 ...

  8. Python变量访问权限控制

    oop1.py文件代码 # user/bin/python class Foo: def bar(self): print('ok') def hello(self, name): print(&qu ...

  9. Shell 编程(循环)

    for in 循环语句 #!/bin/bash for x in one two three four do echo number $x done 例:取出passwd中每一行name 并输出 he ...

  10. 2018面向对象程序设计(Java)第4周学习指导及要求

    2018面向对象程序设计(Java) 第4周学习指导及要求(2017.9.19-2017.9. 26)   学习目标 掌握类与对象的基础概念,理解类与对象的关系: 掌握对象与对象变量的关系: 掌握预定 ...