前言:在本文中,我将介绍如何在通用主机之上重新构建ASP.NET Core 3.0,以及由此带来的一些好处。 同时也展示了3.0中引入新的抽象类IHostLifetime,并描述了它在管理应用程序(尤其是worker services)的生命周期中的作用。在文章的后半部分,我会详细介绍类之间的交互及其在应用程序启动和关闭期间的角色。 同时也会详细介绍通常不需要我们处理的事情,即使不需要关心,但是它对于我们理解其原理也很有用!

翻译:Andrew Lock   https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/

探索ASP.NET Core 3.0系列一:新的项目文件、Program.cs和generic host

探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs

探索 ASP.Net Core 3.0系列三:ASP.Net Core 3.0中的Service provider validation

探索ASP.Net Core 3.0系列四:在ASP.NET Core 3.0的应用中启动时运行异步任务

探索ASP.Net Core 3.0系列六:ASP.NET Core 3.0新特性启动信息中的结构化日志

一、背景:将ASP.NET Core重新平台化到通用主机上

(1)ASP.NET Core 3.0的主要特点之一是整个都已基于.NET Generic Host进行了重写。 .NET Generic Host 是ASP.NET Core 2.1中引入的,是ASP.NET Core使用的现有WebHost的“非Web”版本。 Generic Host允许您在非Web场景中重用Microsoft.Extensions的许多DI,配置和日志记录抽象。

(2)虽然这绝对是一个令人羡慕的目标,但在实现中也存在一些问题。 通用主机实质上复制了ASP.NET Core所需的许多抽象,创建了直接等效项,但使用的是不同的命名空间。 IHostingEnvironment是该问题的一个很好的例子-自1.0版以来,它就已经存在于ASP.NET Core中Microsoft.AspNetCore.Hosting中。 但是在版本2.1中,在Microsoft.Extensions.Hosting命名空间中添加了新的IHostingEnvironment。 即使接口是相同的,但是两者都有导致通用库尝试使用抽象的问题。

(3)使用3.0,ASP.NET Core团队进行重大更改,直接解决此问题。 他们不必重新编写两个单独的Hosts,而是可以重新编写ASP.NET Core堆栈,使其位于 .NET generic host之上。 这意味着它可以真正重用相同的抽象,从而解决了上述问题。 希望在通用主机之上构建其他非HTTP堆栈(例如ASP.NET Core 3.0中引入的gRPC功能)的部分动机也促成了此举。

(4)但是,对于ASP.NET Core 3在通用主机之上进行“重建”或“重新平台化”的真正含义是什么? 从根本上讲,这意味着Kestrel Web服务器(处理HTTP请求和对中间件管道的调用)现在作为IHostedService运行。而当您的应用程序启动时,Kestrel现在只是在后台运行的另一项服务。

注意:值得强调的一点是,您在ASP.NET Core 2.x应用程序中使用的现有WebHost和WebHostBuilder实现在3.0中不会消失。 它们不再是推荐的方法,但是并没有被删除,甚至没有被标记为过时。 我希望它们会在下一个主要版本中被标记为过时,因此值得考虑进行切换。

简单介绍了背景。 我们有一个通用主机,而Kestrel作为IHostedService运行。 但是,ASP.NET Core 3.0中引入的另一个功能是IHostLifetime接口,该接口允许使用其他托管模型。

二、Worker services 和新的 IHostLifetime 接口

ASP.NET Core 3.0引入了“worker services”的概念以及相关的新应用程序模板。 Worker services旨在为您提供可长时间运行的应用程序,您可以将它们安装为Windows服务或系统服务。 这些服务有两个主要功能:

  • 他们通过实现 IHostedService来实现后台应用。
  • 他们通过一个实现了IHostLifetime接口的类来管理应用程序的生命周期。

下面我们先来创建一个Worker services,看看长啥样子:

鼠标放到 BackgroundService F12,你会发现,原来如此:

using System;
using System.Threading;
using System.Threading.Tasks; namespace Microsoft.Extensions.Hosting
{
//
// 摘要:
// /// Base class for implementing a long running Microsoft.Extensions.Hosting.IHostedService.
// ///
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask; private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); //
// 摘要:
// /// This method is called when the Microsoft.Extensions.Hosting.IHostedService
// starts. The implementation should return a task that represents /// the lifetime
// of the long running operation(s) being performed. ///
//
// 参数:
// stoppingToken:
// Triggered when Microsoft.Extensions.Hosting.IHostedService.StopAsync(System.Threading.CancellationToken)
// is called.
//
// 返回结果:
// A System.Threading.Tasks.Task that represents the long running operations.
protected abstract Task ExecuteAsync(CancellationToken stoppingToken); //
// 摘要:
// /// Triggered when the application host is ready to start the service. ///
//
// 参数:
// cancellationToken:
// Indicates that the start process has been aborted.
public virtual Task StartAsync(CancellationToken cancellationToken)
{
_executingTask = ExecuteAsync(_stoppingCts.Token);
if (_executingTask.IsCompleted)
{
return _executingTask;
}
return Task.CompletedTask;
} //
// 摘要:
// /// Triggered when the application host is performing a graceful shutdown. ///
//
// 参数:
// cancellationToken:
// Indicates that the shutdown process should no longer be graceful.
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
if (_executingTask != null)
{
try
{
_stoppingCts.Cancel();
}
finally
{
await Task.WhenAny(_executingTask, Task.Delay(-, cancellationToken));
}
}
} public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
}

很清晰,BackgroundService 继承了 IHostedService。IHostedService已经存在了很长时间,并且允许您运行后台服务。 第二点很有趣。 IHostLifetime接口是.NET Core 3.0的新增功能,它具有两种方法:

public interface IHostLifetime
{
Task WaitForStartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}

在稍后的部分中,我们将详细介绍IHostLifetime,但总结如下:

  • 通用主机启动时将调用WaitForStartAsync,可将其用于监听关闭事件或延迟应用程序的启动,直到发生某些事件为止。
  • 通用主机停止时调用StopAsync。

.NET Core 3.0当前存在三种不同的IHostLifetime实现:

  • ConsoleLifetime –监听SIGTERM或Ctrl + C并停止主机应用程序。
  • SystemdLifetime –监听SIGTERM并停止主机应用程序,并通知systemd有关状态更改(“Ready”和“Stopping”)
  • Windows ServiceLifetime –挂钩Windows Service事件以进行生命周期管理

默认情况下,通用主机使用ConsoleLifetime,它提供了您在ASP.NET Core 2.x中惯用的行为,当应用程序从控制台接收到SIGTERM信号或Ctrl + C时,应用程序将停止。 创建Worker Service(Windows或systemd服务)时,主要是为该应用程序配置IHostLifetime。

三、理解应用的启动

当我在研究这种新的抽象时,开始感到非常困惑。 什么时候会被调用? 它与ApplicationLifetime有什么关系? 谁首先调用了IHostLifetime? 为了使事情更清晰,我花了一些时间来查找ASP.NET Core 3.0中默认应用程序中他们之间的交互。

在这篇文章中,我们从默认的ASP.NET Core 3.0 Program.cs文件开始。

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
} public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}

特别是,一旦构建了通用Host对象,我会对Run()调用的功能感兴趣。

请注意,我不会对代码进行详尽的描述-我将跳过任何我认为无关紧要的内容。 我的目标是对交互有一个整体感觉。如果您想更深入一点,可以 查看源代码!

Run()是HostingAbstractionsHostExtensions的扩展方法,它调用RunAsync()并阻塞直到该方法退出。 当该方法退出时,应用程序退出,因此所有有趣的事情都在那里发生了! 下图概述了RunAsync()中发生的情况,下面将讨论详细信息:

(图片来自于:https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/

Program.cs调用Run()扩展方法,该方法调用RunAsync()扩展方法。 依次调用IHost实例上的StartAsync()。 StartAsync方法可以完成诸如启动IHostingServices(稍后将介绍)之类的许多工作,但是该方法在被调用后会很快返回。

接下来,RunAsync()方法调用另一个扩展方法,称为WaitForShutdownAsync()。 此扩展方法执行图中所示的所有其他操作。 这个名字很具描述性。 此方法对其自身进行配置,以使其暂停,直到触发IHostApplicationLifetime上的ApplicationStopping取消令牌为止(我们将很快了解如何触发该令牌)。

扩展方法WaitForShutdownAsync()使用TaskCompletionSource并等待关联的Task来实现此目的。 这不是我以前需要使用的模式,它看起来很有趣,因此我在下面添加了它(改编自HostingAbstractionsHostExtensions)

public static async Task WaitForShutdownAsync(this IHost host)
{
// Get the lifetime object from the DI container
var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>(); // Create a new TaskCompletionSource called waitForStop
var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); // Register a callback with the ApplicationStopping cancellation token
applicationLifetime.ApplicationStopping.Register(obj =>
{
var tcs = (TaskCompletionSource<object>)obj; // When the application stopping event is fired, set
// the result for the waitForStop task, completing it
tcs.TrySetResult(null);
}, waitForStop); // Await the Task. This will block until ApplicationStopping is triggered,
// and TrySetResult(null) is called
await waitForStop.Task; // We're shutting down, so call StopAsync on IHost
await host.StopAsync();
}

此扩展方法说明了应用程序如何在运行状态下“暂停”,而所有内容都在后台任务中运行。 让我们更深入地了解上图顶部的IHost.StartAsync()方法调用。

四、Host.StartAsync()

在上图中,我们研究了在接口IHost上运行的HostingAbstractionsHostExtensions扩展方法。 如果我们想知道在调用IHost.StartAsync()时通常会发生什么,那么我们需要查看一个具体的实现。 下图显示了实际使用的通用Host实现的StartAsync()方法。 同样,我们来 看看以下有趣的部分。

从上图可以看到,这里还有很多步骤! 对Host.StartAsync()的调用是通过在本文前面介绍的IHostLifetime实例上调用WaitForStartAsync()开始的。 此时的行为取决于您使用的是哪个IHostLifetime,但是我将假定我们正在为本文使用ConsoleLifetime(ASP.NET Core应用程序的默认设置)。

注意:SystemdLifetime的行为与ConsoleLifetime非常相似,并具有一些额外的功能。 WindowsServiceLifetime是完全不同的,并且派生自System.ServiceProcess.ServiceBase。

ConsoleLifetime.WaitForStartAsync()方法(如下所示)做了一件重要的事情:它为控制台中的SIGTERM请求和Ctrl + C添加了事件侦听器。 请求关闭应用程序时将触发这些事件。 因此,通常由IHostLifetime负责控制应用程序何时关闭。

public Task WaitForStartAsync(CancellationToken cancellationToken)
{
// ... logging removed for brevity // Attach event handlers for SIGTERM and Ctrl+C
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
Console.CancelKeyPress += OnCancelKeyPress; // Console applications start immediately.
return Task.CompletedTask;
}

如上面的代码所示,此方法立即完成,并将控制权返回给Host.StartAsync()。 此时,主机加载所有IHostedService实例,并在每个实例上调用StartAsync()。 这包括用于启动Kestrel Web服务器的GenericWebHostService(该服务器最后启动)。

一旦所有IHostedServices已启动,Host.StartAsync()就会调用IHostApplicationLifetime.NotifyStarted()来触发所有已注册的回调(通常只是记录)并退出。

请注意,IhostLifetime与IHostApplicationLifetime不同。 前者包含用于控制应用程序启动时间的逻辑。 后者(由ApplicationLifetime实现)包含CancellationTokens,您可以根据它们注册回调以在应用程序生命周期的各个时间点运行。

此时,应用程序处于“运行”状态,所有后台服务都在运行,Kestrel处理请求,并且原始的WaitForShutdownAsync()扩展方法等待ApplicationStopping事件触发。 最后,让我们看一下在控制台中键入Ctrl + C时会发生什么。

五、shutdown process

当ConsoleLifetime从控制台接收到SIGTERM信号或Ctrl + C(取消键)时,将发生关闭过程。 下图显示了关闭过程中所有关键参与者之间的相互作用:

(1)触发Ctrl + C终止事件时,ConsoleLifetime会调用IHostApplicationLifetime.StopApplication()方法。 这将触发所有使用ApplicationStopping取消令牌注册的回调。 如果回头看一下程序概述,您将看到触发器是原始RunAsync()扩展方法正在等待的触发器,因此等待的任务完成,并调用了Host.StopAsync()。

(2)Host.StopAsync()开始通过再次调用IHostApplicationLifetime.StopApplication()。 第二次调用是第二次运行时的noop,但这是必需的,因为从技术上讲,还有其他触发Host.StopAsync()的方式。

(3)接下来,主机以相反的顺序关闭所有IHostedServices。 首先启动的服务将最后停止,因此GenericWebHostedService首先被关闭。

(4)关闭服务后,将调用IHostLifetime.StopAsync,它对于ConsoleLifetime是noop(空操作)。 最后,Host.StopAsync()在退出之前调用IHostApplicationLifetime.NotifyStopped()以通知任何关联的处理程序。

(5)此时,一切都关闭,Program.Main函数退出,应用程序退出。

六、总结

在这篇文章中,聊了一些有关如何在通用主机之上重新构建ASP.NET Core 3.0的背景,并介绍了新的IHostLifetime接口。 然后,我详细描述了使用通用主机的典型ASP.NET Core 3.0应用程序的启动和关闭所涉及的各种类和接口之间的交互。如果还是不明白的同学可以查看源码,希望它对你有所帮助!

翻译:Andrew Lock   https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/

作者:郭峥

出处:http://www.cnblogs.com/runningsmallguo/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

探索 ASP.Net Core 3.0系列五:引入IHostLifetime并弄清Generic Host启动交互的更多相关文章

  1. 探索ASP.NET Core 3.0系列一:新的项目文件、Program.cs和generic host

    前言:在这篇文章中我们来看看ASP.Net Core 3.0应用程序中一些基本的部分—— .csproj项目文件和Program.cs文件.我将会介绍它们从 ASP.NET Core 2.x 中的默认 ...

  2. 探索ASP.Net Core 3.0系列四:在ASP.NET Core 3.0的应用中启动时运行异步任务

    前言:在本文中,我将介绍ASP.NET Core 3.0 WebHost的微小更改如何使使用IHostedService在应用程序启动时更轻松地运行异步任务. 翻译 :Andrew Lock   ht ...

  3. 探索 ASP.Net Core 3.0系列三:ASP.Net Core 3.0中的Service provider validation

    前言:在本文中,我将描述ASP.NET Core 3.0中新的“validate on build”功能. 这可以用来检测您的DI service provider是否配置错误. 具体而言,该功能可检 ...

  4. 探索ASP.Net Core 3.0系列六:ASP.NET Core 3.0新特性启动信息中的结构化日志

    前言:在本文中,我将聊聊在ASP.NET Core 3.0中细小的变化——启动时记录消息的方式进行小的更改. 现在,ASP.NET Core不再将消息直接记录到控制台,而是正确使用了logging 基 ...

  5. 探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs

    原文:探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs 前言:.NET Core 3.0 SDK包含比以前版本更多的现成模板. 在本文中,我将 ...

  6. [转]探索ASP.NET Core 3.0 系列

    这是该系列的第一篇文章:探索ASP.NET Core 3.0. 第1部分-探索新的项目文件Program.cs和通用主机(本文) 第2部分-比较ASP.NET Core 3.0模板之间的Startup ...

  7. ASP.NET Core 2.0 : 系列目录

    目录: ASP.NET Core 2.0 : 一. 概述 ASP.NET Core 2.0: 二. 开发环境 ASP.NET Core 2.0 : 三. 项目结构 ASP.NET Core 2.0 : ...

  8. [译]ASP.NET Core 2.0 系列文章目录

    基础篇 [译]ASP.NET Core 2.0 中间件 [译]ASP.NET Core 2.0 带初始参数的中间件 [译]ASP.NET Core 2.0 依赖注入 [译]ASP.NET Core 2 ...

  9. 学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. Filter 原理

    二.Filter 原理 2.1 Filter 概述 Filter(过滤器)是 DirectShow 中最基本的概念.DirectShow 是通过 Filter Graph 来管理 Filter 的.F ...

  2. idea 方法注释live template

    groovyScript("def result=''; def params="${_1}".replaceAll('[\\[|\\]|\\s]', '').split ...

  3. Sql 代码规范说明

    对于程序工作者来说,代码的阅读必不可少,好的代码让人读起来一目了然.神清气爽,做代码调试也可以很开的捋顺逻辑定位问题,但是如果遇到一些可读性较差,毫无规矩可言的代码,那真的比吃了翔都难受啊,如果再让你 ...

  4. PAT 1002 A+B for Polynomials(map模拟)

    This time, you are supposed to find A+B where A and B are two polynomials(多项式). Input Each input fil ...

  5. Windows 10 powershell 中文乱码解决方案

    Windows 10 powershell 中文乱码解决方案 Intro 我装的系统是英文版的 win 10 操作系统,最近使用命令行测试接口,发现中文显示一直异常, 使用网上的各种解决方案都没有效果 ...

  6. oracle学习笔记(二十一) 程序包

    程序包 之前我们调用的dbms_output.put_line(''),dbms_output就是一个程序包 程序包创建语法 1. 声明程序包 --声明程序包中的过程,函数,自定义的类型 --程序包里 ...

  7. .net post请求wcf

    class Program { static void Main(string[] args) { }); var r = HttpHelper.PostRequest("http://lo ...

  8. 13. 罗马数字转整数(C#)

    看到这道题,存在键值对,所以先建个泛型字典,把键值填进去. 由于这道题存在两个字符表示一个数字的情况,所以在for循环的时候判断一下,看看当前字符串中循环到的字符是否和下一个字符能够组成存在在字典里的 ...

  9. 点击事件的坐标计算(client || offset) +(X || Width || Left) 各种排列组合别绕晕

    结论: 1,X,Y的都是属于点击位置的,width.height.left.top都是属于DOM的. 2,涉及的所有位置只与document或DOM内部有关,与DOM如何定位,周围有没有其他占位HTM ...

  10. npm命令集合

    1.npm初始化项目:npm init 或者 npm init --yes 生产 package.json文件 2.下载package.json安装包: npm install 缩写 npm i