探索 ASP.Net Core 3.0系列五:引入IHostLifetime并弄清Generic Host启动交互

摘要:
同时也展示了3.0中引入新的抽象类IHostLifetime,并描述了它在管理应用程序的生命周期中的作用。.NETGenericHost是ASP.NETCore2.1中引入的,是ASP.NETCore使用的现有WebHost的“非Web”版本。他们不必重新编写两个单独的Hosts,而是可以重新编写ASP.NETCore堆栈,使其位于.NETgenerichost之上。我们有一个通用主机,而Kestrel作为IHostedService运行。但是,ASP.NETCore3.0中引入的另一个功能是IHostLifetime接口,该接口允许使用其他托管模型。

前言:在本文中,我将介绍如何在通用主机之上重新构建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,看看长啥样子:

探索 ASP.Net Core 3.0系列五:引入IHostLifetime并弄清Generic Host启动交互第1张

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

usingSystem;
usingSystem.Threading;
usingSystem.Threading.Tasks;
namespaceMicrosoft.Extensions.Hosting
{
    //
    //摘要:
    //     /// Base class for implementing a long running Microsoft.Extensions.Hosting.IHostedService.
    //     ///
    public abstract classBackgroundService : IHostedService, IDisposable
    {
        privateTask _executingTask;
        private readonly CancellationTokenSource _stoppingCts = newCancellationTokenSource();
        //
        //摘要:
        //     /// 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 abstractTask ExecuteAsync(CancellationToken stoppingToken);
        //
        //摘要:
        //     /// Triggered when the application host is ready to start the service. ///
        //
        //参数:
        //cancellationToken:
        //Indicates that the start process has been aborted.
        public virtualTask StartAsync(CancellationToken cancellationToken)
        {
            _executingTask =ExecuteAsync(_stoppingCts.Token);
            if(_executingTask.IsCompleted)
            {
                return_executingTask;
            }
            returnTask.CompletedTask;
        }
        //
        //摘要:
        //     /// Triggered when the application host is performing a graceful shutdown. ///
        //
        //参数:
        //cancellationToken:
        //Indicates that the shutdown process should no longer be graceful.
        public virtual asyncTask StopAsync(CancellationToken cancellationToken)
        {
            if (_executingTask != null)
            {
                try
                {
                    _stoppingCts.Cancel();
                }
                finally
                {
                    await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
                }
            }
        }
        public virtual voidDispose()
        {
            _stoppingCts.Cancel();
        }
    }
}

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

public interfaceIHostLifetime
{
    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 classProgram
{
    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()中发生的情况,下面将讨论详细信息:

探索 ASP.Net Core 3.0系列五:引入IHostLifetime并弄清Generic Host启动交互第2张

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

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

探索 ASP.Net Core 3.0系列五:引入IHostLifetime并弄清Generic Host启动交互第3张

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

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

public static async Task WaitForShutdownAsync(thisIHost 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
    awaitwaitForStop.Task;
    //We're shutting down, so call StopAsync on IHost
    awaithost.StopAsync();
}

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

四、Host.StartAsync()

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

探索 ASP.Net Core 3.0系列五:引入IHostLifetime并弄清Generic Host启动交互第4张

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

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

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

publicTask 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.
    returnTask.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(取消键)时,将发生关闭过程。 下图显示了关闭过程中所有关键参与者之间的相互作用:

探索 ASP.Net Core 3.0系列五:引入IHostLifetime并弄清Generic Host启动交互第5张

(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启动交互》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇C#中文和UNICODE编码转换LaTeX技巧23:BIBTeX制作参考文献下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Shiro权限管理框架(四):深入分析Shiro中的Session管理

其实关于Shiro的一些学习笔记很早就该写了,因为懒癌和拖延症晚期一直没有落实,直到今天公司的一个项目碰到了在集群环境的单点登录频繁掉线的问题,为了解决这个问题,Shiro相关的文档和教程没少翻。最后问题解决了,但我觉得我也是时候来做一波Shiro学习笔记了。 本篇是Shiro系列第四篇,Shiro中的过滤器初始化流程和实现原理。Shiro基于URL的权限...

YAML 模板文件语法

YAML 模板文件语法 默认的模板文件是 docker-compose.yml,其中定义的每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)来自动构建。 其它大部分指令都跟 docker run 中的类似。 如果使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOL...

如何更优雅地切换测试、正式环境?

初学者是怎么做的? 小明一个刚入行安卓的小萌新,刚刚在测试小姐姐那里交过学费(挨过骂)了解到软件开发过程中是需要区分正式、测试环境的。但是他稍加思考就能想到测试、正式环境的区别仅仅是host不一样而已,其他的比如接口名、参数名、返回的json格式均一模一样。于是他马上找到了解决方案,平时都用测试环境的,到上线的时候再换回正式环境不就可以了?在一次开发中需要...

wireshark筛选器汇总

抓取指定IP地址的数据流: 如果你的抓包环境下有很多主机正在通讯,可以考虑使用所观察主机的IP地址来进行过滤。以下为IP地址抓包过滤示例: host 10.3.1.1:抓取发到/来自10.3.1.1的数据流 host 2406:da00:ff00::6b16:f02d:抓取发到/来自IPv6地址2406:da00:ff00::6b16:f02d的数据流...

安装arm-linux-gcc编译器时出现错误,请大神看看怎么回事

echo '/opt/buildroot-2011.05/output/toolchain/gcc-4.3.5/gcc/ada/decl.c' >> tmp-gi.listecho '/opt/buildroot-2011.05/output/toolchain/gcc-4.3.5/gcc/ada/trans.c' >> tmp-g...

Ansible 系列之 Inventory 资源清单介绍

一、Inventory 库存清单文件 1.Inventory 作用 Ansible 可以在同一时间针对多个系统设施进行管理工作。它通过选择Ansible 资源清单文件中列出的系统,该清单文件默认是在/etc/ansible/hosts,也可以使用 -i <path> 进行路径的指定。文件内的格式INI 风格,中括号内为分组名。 除了这个文件之...