在《歷數依賴注入的N種玩法》演示系統自動註冊服務的實例中,咱們會發現輸出的列表包含兩個特殊的服務,它們的對應的服務接口分別是IApplicationLifetime和IHostingEnvironment,咱們將分別實現這兩個接口的服務統稱在ApplicationLifetime和HostingEnvironment。咱們從其命名便可以看出ApplicationLifetime與應用的聲明週期有關,而HostingEnvironment則用來表示當前的執行環境,本篇文章咱們着重來了解ApplicationLifetime與整個AASP.NET Core應用的生命週期有何關係。[本文已經同步到《ASP.NET Core框架揭祕》之中]html
目錄
1、ApplicationLifetime
2、WebHost的Run方法
3、遠程關閉應用服務器
1、ApplicationLifetime
從命名的角度來看,ApplicationLifetime貌似是對當前應用生命週期的描述,而實際上它存在的目的僅僅是在應用啓動和關閉時對相關組件發送相應的信號或者通知而已。以下面的代碼片斷所示,IApplicationLifetime接口具備三個CancellationToken類型的屬性(ApplicationStarted、ApplicationStopping和ApplicationStopped),若是須要在應用自動和終止先後執行某種操做,咱們能夠註冊相應的回調在這三個CancellationToken對象上。除了這三個類型爲CancellationToken的屬性,IApplicationLifetime接口還定義了一個StopApplication方法,咱們能夠調用這個方法發送關閉應用的信號,並最終真正地關閉應用。app
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方法。async
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: }
2、WebHost的Run方法
咱們知道啓動應用最終是經過調用做爲宿主的WebHost的Start方法來完成的,可是咱們以前演示的全部實例都未曾顯式地調用過這個方法,咱們調用的是它的擴展方法Run。毫無疑問,WebHost的Run方法確定會調用Start方法來開啓WebHost,可是除此以外,這個Run方法還有何特別之處呢?ide
Run方法的目的除了啓動WebHost以外,它實際上會阻塞當前進程直到應用關閉。咱們知道應用的關閉的意圖是經過利用ApplicationLifetime發送相應信號的方式實現的,因此這個Run方法在啓動WebHost的時候,會以阻塞當前線程的方式等待直至接收到這個信號。以下所示的代碼片斷基本上體現了這兩個擴展方法Run的實現邏輯。post
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。this
3、遠程關閉應用
既然WebHost在啓動以後會利用ApplicationLifetime等待Stopping信號的發送,這就意味着組成ASP.NET Core管道的服務器和任何一箇中間件均可以在適當的時候調用ApplicationLifetime的StopApplication來關閉應用。對於《服務器在管道中的「龍頭」地位》介紹的KestrelServer,咱們知道在構造這個對象的時候必須指定一個ApplicationLifetime對象,其根本的目的在於當發送某些沒法恢復的錯誤時,它能夠利用這個對象關閉應用。spa
接下來咱們經過實例的方式來演示如何在一箇中間件中利用這個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...