.NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务

        在这篇文章中,我们将了解 .NET 8 中为托管服务引入的一些新生命周期事件。请注意,这篇文章与 .NET 8 相关,在撰写本文时,.NET 8 目前处于预览状态。在 11 月 .NET 8 最终版本发布之前,类型和实现可能会发生变化。要继续操作,您将需要.NET 8:Announcing .NET 8 - .NET Blog。

 IHostedLifecycleService 简介

        主要更改是在 Microsoft.Extensions.Hosting 命名空间中包含一个名为 IHostedLifecycleService 的新接口。此接口继承自现有的 IHostedService 接口,对其进行扩展以添加在现有 StartAsync 和 StopAsync 方法之前或之后发生的新生命周期事件的方法。这些提供了一种方法来挂钩某些高级场景的更具体的应用程序生命周期事件。

接口定义如下:

public partial interface IHostedLifecycleService : Microsoft.Extensions.Hosting.IHostedService
{Task StartingAsync(CancellationToken cancellationToken);Task StartedAsync(CancellationToken cancellationToken);Task StoppingAsync(CancellationToken cancellationToken);Task StoppedAsync(CancellationToken cancellationToken);    
}

        实现此接口的所有已注册托管服务的 StartAsync 方法将在应用程序生命周期的早期运行,然后在任何已注册托管服务上调用 StartAsync(来自 IHostedService)。这可用于在应用程序启动之前执行一些非常早期的验证检查,例如检查关键需求或可用的依赖项。这使得应用程序可能在任何托管服务开始执行其主要工作负载之前启动失败。其他用途包括“预热”和初始化单例以及应用程序使用的其他状态。

        注册托管服务的所有 StartAsync(来自 IHostedService)方法完成后,将在实现上调用 StartedAsync。这可用于在将应用程序标记为成功启动之前验证应用程序状态或条件。

        StoppingAsync 和 StoppedAsync 在应用程序关闭期间的工作方式类似,并为关闭前和关闭后验证提供高级挂钩。

        在讨论更详细的细节之前,值得讨论一下为什么 Microsoft 创建了一个新的派生接口,而不是利用默认接口实现来更新现有的 IHostedService 接口。对于默认接口实现来说,这确实是一个很好的例子,可以将它们添加到具有默认无操作实现的 IHostedService 中。当我们查看该库的运行时目标时,原因很明显。托管包多目标各种目标框架。这包括netstandard2.0,它在引入默认接口实现功能之前就被锁定了。因此,为了继续支持这个目标,改用衍生的接口设计。

        作为引入此接口的 PR 的一部分,HostOptions 中还添加了一个新选项 StartupTimeout。这允许提供一个 TimeSpan 来控制所有托管服务启动所允许的最长时间。当配置为非无限值(默认值)时,传递给启动生命周期事件的取消令牌将链接到使用提供的值配置的 CancellationTokenSource。

使用新界面
        使用 .NET 8 ,我们可以查看如何使用此新界面的一般示例。我在应用程序中看到的一项相当常见的启动工作是初始化数据库。

        在生产过程中,我们可以预期此类数据库是在线的、可用的和种子的;在其他环境中,例如 CI,我们可能需要创建一个虚拟数据库并使用示例数据进行播种。存在多种解决方案来处理此问题,但一种可能的选择是使用托管服务有条件地执行工作。当其他托管服务依赖于可用数据库时,这可能会更加复杂,因为这些服务必须在数据库准备就绪后以正确的顺序启动。在 .NET 7 中,这是可以实现的,因为托管服务按顺序并按照注册顺序启动。

        因此,在 .NET 7 中,我们可以实现以下目标:

public class ServiceA : IHostedService
{public Task StartAsync(CancellationToken cancellationToken){// INIT DBreturn Task.CompletedTask;}public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}public class ServiceB : BackgroundService
{protected override Task ExecuteAsync(CancellationToken stoppingToken){// USE DBreturn Task.CompletedTask;}
}

为了向 DI 容器注册这些服务以便主机执行它们,我们必须确保以正确的顺序专门添加它们。

builder.Services.AddHostedService<ServiceA>();

builder.Services.AddHostedService<ServiceB>();

        由于 .NET 7 按顺序而不是同时执行每个服务的 StartAsync 方法,因此我们知道,在调用 ServiceB.StartAsync 时,数据库应该已在 ServiceA 中完成初始化。

        虽然默认情况下在 .NET 8 中也是如此,但现在也可以配置主机以同时启动它们。如果我们想更改此选项,我们的应用程序可能会崩溃,因为 ServiceB 会与 ServiceA 同时触发。这可能不是一个重要问题,但如果应用程序中有其他托管服务,通过切换到并发执行,我们可以减少应用程序的整体启动时间。

        通过在 .NET 8 中引入新的 IHostedLifecycleService,我们可以将数据库初始化工作移至生命周期的早期,同时还可以利用并发托管服务启动。

public class ServiceA : IHostedService, IHostedLifecycleService
{public Task StartingAsync(CancellationToken cancellationToken){// INIT DBreturn Task.CompletedTask;}public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;public Task StoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask;public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}public class ServiceB : BackgroundService
{protected override Task ExecuteAsync(CancellationToken stoppingToken){// USE DBreturn Task.CompletedTask;}
}

        在上面的示例代码中,我们定义了两个托管服务。除了 IHostedService 之外,ServiceA 还实现了新的 IHostedLifecycleService。我们希望在应用程序生命周期的早期、任何主要工作负载之前执行数据库初始化。因此,我们可以将数据库设置代码包含在 GettingAsync 方法中。

        派生自BackgroundService 的ServiceB 现在可以在其ExecuteAsync 方法内安全地使用数据库,因为ExecuteAsync 是由IHostedService 接口中定义的StartAsync 的底层实现调用的。因此,在注册服务的所有 StartingAsync 方法完成之前,不会调用它。

        我们将以与 .NET 中相同的方式向 DI 容器注册这些服务,但添加它们的顺序不再重要。

builder.Services.AddHostedService<ServiceB>();

builder.Services.AddHostedService<ServiceA>();

        即使按照这个顺序,ServiceA的StartingAsync也会在ServiceB.StartAsync之前执行。我们甚至可以配置并发启动和停止行为,而不会破坏我们的逻辑。

builder.Services.Configure<HostOptions>(options =>

{

    options.ServicesStartConcurrently = true;

    options.ServicesStopConcurrently = true;

});

深入细节

  引入新接口后,最核心的更改是在内部 Host 类内部实现的,该类实现了 IHost 接口。此类定义了主 Host,在从 ASP.NET Core 和 Worker Service 等模板创建新应用程序时构建。IHost 接口定义了当应用程序启动或停止时调用的 StartAsync 和 StopAsync 方法。

  第一个有意义的更改在 StartAsync 方法的开头引入了额外的逻辑,以实现新的StartupTimeout 功能。

CancellationTokenSource? cts = null;
CancellationTokenSource linkedCts;
if (_options.StartupTimeout != Timeout.InfiniteTimeSpan)
{cts = new CancellationTokenSource(_options.StartupTimeout);linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken, _applicationLifetime.ApplicationStopping);
}
else
{linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
}

        从更新后的代码中我们可以看到,在所有情况下,传递到 StartAsync 的取消令牌都可能导致取消,就像 IHostApplicationLifetime 上公开的 ApplicationStopping 令牌一样,如果触发关闭,该令牌将被标记为已取消。

        当 HostOptions.StartupTimeout 不等于 InfiniteTimeSpan 时,将创建 CancellationTokenSource,并将 TimeSpan 传递到构造函数中。然后可以将其令牌添加到链接的令牌源,以确保第三个条件也可以触发中止启动。这个新选项允许应用程序开发人员为预期的“正常”启动提供预期的上限时间,这样在特殊情况下,长时间的延迟就可以主动触发启动的中止。这种情况可能是由不可用的外部依赖项引起的,应进行跟踪和记录,以便进行调查。

        创建 linkedCts 后,它将用于访问 CancellationToken,然后将其传递到启动过程中调用的任何后续异步方法中。

CancellationToken token = linkedCts.Token;

        第一个异步方法是 IHostLifetime.WaitForStartAsync 的调用,这是托管生命周期中的早期挂钩,此时正在等待。这是另一个高级挂钩,可能会延迟启动,直到收到外部事件信号为止。这个概念从 .NET Core 3.0 开始就已经存在。

// This may not catch exceptions.

await _hostLifetime.WaitForStartAsync(token).ConfigureAwait(false);        

token.ThrowIfCancellationRequested();

实现中的下一行准备一些变量和字段。

List<Exception> exceptions = new();

_hostedServices = Services.GetRequiredService<IEnumerable<IHostedService>>();

_hostedLifecycleServices = GetHostLifecycles(_hostedServices);

bool concurrent = _options.ServicesStartConcurrently;

bool abortOnFirstException = !concurrent;

        设置列表以包含启动期间的任何异常后,将从容器中检索所有已注册的 IHostedService。GetHostLifecycles 方法用于循环 IHostedService 实现并确定哪个(如果有)也实现 IHostedLifecycleService。

下一段代码为每个 IHostedLifecycleService 执行 GettingAsync 方法。

if (_hostedLifecycleServices is not null)
{// Call StartingAsync().await ForeachService(_hostedLifecycleServices, token, concurrent, abortOnFirstException, exceptions,(service, token) => service.StartingAsync(token)).ConfigureAwait(false);
}

        ForeachService 是一个辅助方法,它根据作为参数传入的 HostOptions 设置并发或顺序执行服务。

private static async Task ForeachService<T>(IEnumerable<T> services,CancellationToken token,bool concurrent,bool abortOnFirstException,List<Exception> exceptions,Func<T, CancellationToken, Task> operation)
{if (concurrent){// The beginning synchronous portions of the implementations are run serially in registration order for// performance since it is common to return Task.Completed as a noop.// Any subsequent asynchronous portions are grouped together run concurrently.List<Task>? tasks = null;foreach (T service in services){Task task;try{task = operation(service, token);}catch (Exception ex){exceptions.Add(ex); // Log exception from sync method.continue;}if (task.IsCompleted){if (task.Exception is not null){exceptions.AddRange(task.Exception.InnerExceptions); // Log exception from async method.}}else{tasks ??= new();tasks.Add(Task.Run(() => task, token));}}if (tasks is not null){Task groupedTasks = Task.WhenAll(tasks);try{await groupedTasks.ConfigureAwait(false);}catch (Exception ex){exceptions.AddRange(groupedTasks.Exception?.InnerExceptions ?? new[] { ex }.AsEnumerable());}}}else{foreach (T service in services){try{await operation(service, token).ConfigureAwait(false);}catch (Exception ex){exceptions.Add(ex);if (abortOnFirstException){return;}}}}
}

        代码基于布尔并发参数进行分支。让我们重点关注用于同时调用每个服务的功能的代码。

        正如注释所述,实现首先在每个服务上调用操作委托,在本例中为“StartingAsync”。许多 IHostedLifecycleService 完全有可能通过返回缓存的 Task.CompletedTask 在其大多数方法上实现无操作。在这些情况下,代码同步运行,因为没有什么可等待的。上面的代码对此进行了特殊处理,并检查是否有任何任务立即返回完成或抛出同步异常。对于这些已完成的任务,其中引发的任何异常都会添加到异常列表中。

        对于此时尚未完成的任何任务,它们都是异步运行的。这些任务将添加到任务列表中。所有任务启动后,就会使用 WhenAll 等待它们,这意味着它们会同时运行,直到所有注册的服务都完成其工作。任何异常也会在这里捕获。

        在非并发路径中,代码更简单,因为它可以简单地按顺序等待每个操作。在此配置中,每个服务必须在调用下一个服务之前完成其工作。

        返回 Host.StartAsync 方法,对 IHostedService.StartAsync 和 IHostedLifecycleService.StartedAsync 重复该过程。该方法最后会记录并重新抛出任何捕获的异常,然后触发托管应用程序现已启动的通知。

        Stopping 和 Stopped 的新生命周期事件的实现几乎相同,因此我们无需在这里深入讨论。

概括
        Microsoft 在每个版本中不断完善和增强 .NET 中的托管概念。在 .NET 8 中,重点放在引入对托管服务的启动行为的更多控制。在这项最新的工作中,他们引入了对启动和关闭之前、期间和之后运行的代码的高级、细粒度控制。新的 IHostedLifecycleService 接口出现在.NET 8 中,现已推出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/630829.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pycharm import torch

目录 1 安装 2 conda环境配置 3 测试 开始学习Pytorch! 1 安装 我的电脑 Windows 11 Python 3.11 Anaconda3-2023.09-0-Windows-x86_64.exe cuda_11.8.0_522.06_windows.exe pytorch &#xff08;管理员命令行安装&#xff09; pycharm-community-2023.3.2.exe 2 c…

白山云基于StarRocks数据库构建湖仓一体数仓的实践

背景 随着每天万亿级别的业务数据流向数据湖&#xff0c;数据湖的弊端也逐渐凸显出来&#xff0c;例如&#xff1a; 数据入湖时效性差&#xff1a;数据湖主要依赖于离线批量计算&#xff0c;通常不支持实时数据更新&#xff0c;因此无法保证数据的强一致性&#xff0c;造成数…

js的防抖与节流

目录 认识防抖与节流防抖节流 手写防抖函数绑定this与参数取消功能立即执行获取返回值最终版 手写节流函数 认识防抖与节流 在JavaScript中&#xff0c;大量操作都会触发事件&#xff0c;这些事件又会被添加到事件队列中进行排队处理 某些事件如果频繁触发的话会对浏览器的性能…

HarmonyOS —— buildMode 设置(对比 Android Build Varient)

前言 在安卓中 Build Variant 主要依赖模块&#xff08;module&#xff09;中 build.gradle 的 BuildType 和 ProductFlavor 提供的属性和方法&#xff0c;我们可以使用 Build Type 可以配置不同的构建方式、ProductFlavor 主要用来进行多渠道打包。 在鸿蒙中要做到同样像效果…

计算机网络——第四层:传输层以及TCP UDP

1. 传输层的协议 1.1 TCP (传输控制协议) - rfc793 连接模式的传输。 保证按顺序传送数据包。 流量控制、错误检测和在数据包丢失时的重传。 用于需要可靠传输的应用&#xff0c;如网络&#xff08;HTTP/HTTPS&#xff09;、电子邮件&#xff08;SMTP, IMAP, POP3&#xff09;…

使用阿里云短信平台发送短信的指南

在当今的数字时代&#xff0c;短信服务在各种应用场景中扮演着重要角色&#xff0c;例如用户认证、事务提醒、营销信息等。阿里云短信服务提供了一种简单、高效的方式来实现这些功能。本文将详细介绍如何使用阿里云短信平台发送短信。 步骤1&#xff1a;注册阿里云账号 首先&…

阿里云服务器4核8G配置最新优惠价格表(2024活动报价)

阿里云服务器4核8g配置云服务器u1价格是955.58元一年&#xff0c;4核8G配置还可以选择ECS计算型c7实例、计算型c8i实例、计算平衡增强型c6e、ECS经济型e实例、AMD计算型c8a等机型等ECS实例规格&#xff0c;规格不同性能不同&#xff0c;价格也不同&#xff0c;阿里云服务器网al…

uniapp uni.chooseLocation调用走失败那里,错误码:112

问题&#xff1a;我配置了百度上所有能配置的&#xff0c;一直调用不成功&#xff0c;如下图配置的 1:第一个 配置 代码&#xff1a; "permission": {"scope.userLocation": {"desc": "你的位置信息将用于小程序位置接口的效果展示"}…

C语言数据结构之线性表-顺序表篇

星光不负赶路人 江河眷顾奋楫者 &#x1f3a5;烟雨长虹&#xff0c;孤鹜齐飞的个人主页 &#x1f525;个人专栏 期待小伙伴们的支持与关注&#xff01;&#xff01;&#xff01; 线性表的简介# 线性表&#xff08;linearlist&#xff09;&#xff1a;是n个具有相同特性的数据元…

css实现动态水波纹效果

效果如下&#xff1a; 外层容器 (shop_wrap)&#xff1a; 设置外边距 (padding) 提供一些间距和边距 圆形容器 (TheCircle)&#xff1a; 使用相对定位 (position: relative)&#xff0c;宽度和高度均为 180px&#xff0c;形成一个圆形按钮圆角半径 (border-radius) 设置为 50%&…

面试题 05.06. 整数转换(力扣)(OJ题)

题目链接&#xff1a;面试题 05.06. 整数转换 - 力扣&#xff08;LeetCode&#xff09; 所属专栏&#xff1a;刷题 整数转换。编写一个函数&#xff0c;确定需要改变几个位才能将整数A转成整数B。 示例1: 输入&#xff1a;A 29 &#xff08;或者0b11101&#xff09;, B 15…

如何实现路由跳转的进度条效果

这里我们可以通过nprogress插件来实现这个效果&#xff0c;配合路由守卫来实现&#xff0c;路由前置守卫开启进度条&#xff0c;路由后置守卫放行进度条即可 安装nprogress pnpm install nprogress 2.编写路由守卫代码 import router from ./indeximport nprogress from nprog…

5-微信小程序语法参考

1. 数据绑定 官网传送门 WXML 中的动态数据均来自对应 Page 的 data。 数据绑定使用 Mustache 语法&#xff08;双大括号&#xff09;将变量包起来 ts Page({data: {info: hello wechart!,msgList: [{ msg: hello }, { msg: wechart }]}, })WXML <view class"vie…

搜索与图论第四期 树与图的广度优先遍历(例题)

例题&#xff1a;快速排序模板&#xff1a; AC代码&#xff1a; 源码&#xff1a; #include <iostream> using namespace std; const int N 1e6 10; int n; int q[N];void quick_sort(int q[], int l, int r) {if (l > r)return ;int x q[l], i l - 1, j r 1…

Ubuntu 22.04 突然失去网络图标,无法ping通等网络消失问题。bug修复

Ubuntu 22.04 突然失去网络图标&#xff0c;无法ping通bug修复 目前主流解决方案如下&#xff0c;But&#xff01; 在我的解决过程中完全失效&#xff0c;固参考一位知乎方案遂解决。 在VM虚拟机上搭建的Ubuntu22.04网络图标突然消失无法联网解决方法 注&#xff1a;这个是我参…

经典目标检测YOLO系列(二)YOLOV2的复现(1)总体网络架构及前向推理过程

经典目标检测YOLO系列(二)YOLOV2的复现(1)总体网络架构及前向推理过程 和之前实现的YOLOv1一样&#xff0c;根据《YOLO目标检测》(ISBN:9787115627094)一书&#xff0c;在不脱离YOLOv2的大部分核心理念的前提下&#xff0c;重构一款较新的YOLOv2检测器&#xff0c;来对YOLOV2有…

压力测试+接口测试(工具jmeter)

jmeter是apache公司基于java开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简单。因 为jmeter是java开发的&#xff0c;所以运行的时候必须先要安装jdk才可以。jmeter是…

【论文阅读】Deep Graph Contrastive Representation Learning

目录 0、基本信息1、研究动机2、创新点3、方法论3.1、整体框架及算法流程3.2、Corruption函数的具体实现3.2.1、删除边&#xff08;RE&#xff09;3.2.2、特征掩盖&#xff08;MF&#xff09; 3.3、[编码器](https://blog.csdn.net/qq_44426403/article/details/135443921)的设…

借用GitHub将typora图片文件快速上传CSDN

前情概要 众所周知&#xff0c;程序员大佬们喜欢用typora软件写代码笔记&#xff0c;写了很多笔记想要放到CSDN上给其他大佬分享&#xff0c;但是在往csdn上搬运的时候&#xff0c;图片总是上传出错&#xff0c;一张一张搞有很麻烦&#xff0c;咋如何搞&#xff1f; 废话不多…

muduo网络库剖析——监听者EpollPoller类

muduo网络库剖析——监听者EpollPoller类 前情从muduo到my_muduo 概要epoll原理解析epoll提供的接口epoll的触发模式epoll实现多路复用 框架与细节成员函数使用方法 源码结尾 前情 从muduo到my_muduo 作为一个宏大的、功能健全的muduo库&#xff0c;考虑的肯定是众多情况是否…