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

这篇具有很好参考价值的文章主要介绍了.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 DB
  
        return Task.CompletedTask;
    }
  
    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
  
public class ServiceB : BackgroundService
{
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // USE DB
  
        return 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 DB
  
        return 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 DB
  
        return 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 中,现已推出。
文章来源地址https://www.toymoban.com/news/detail-808111.html

到了这里,关于.NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 当在IDEA中实现类实现接口时,如果出现红色错误并且无法引入接口类的包,可能是以下几种情况导致的

    1.当在IDEA中实现类实现接口时,如果出现红色错误并且无法引入接口类的包,可能是以下几种情况导致的: 2.缺少依赖:首先检查项目的依赖是否正确配置。如果接口类所在的包不在项目的依赖中,IDEA将无法找到该包并引入。可以通过在项目的构建工具(如Maven或Gradle)中添

    2024年02月17日
    浏览(40)
  • .net实现后台服务就这么简单,只要实现IHostedService接口就行了

      IHostedService  接口在.NET Core中的主要用途是定义应用程序生命周期内运行的后台服务。这些服务可以执行初始化、长时间运行的任务、定期运行的任务等。通过实例,我们可以演示如何使用  IHostedService  来实现一个简单的网络检测服务,并记录日志。 首先,我们需要创建

    2024年02月05日
    浏览(44)
  • 对新的网络服务和应用适应不足:新引入的网络服务和应用未得到适当的安全考虑

    在新的网络技术不断涌现的今天,如何确保这些新技术的安全性至关重要。许多企业和组织都在努力跟上这一发展趋势并满足用户日益增长的需求和期望。然而, 网络安全的形势仍然严峻且复杂多变。“对新的网络服务和应用的适应不足: 新引入的网络服务和应用未得到适当的

    2024年01月17日
    浏览(47)
  • .NET7使用HttpClient实现查询天气预报接口

    朋友做网站需要根据城市展示天气预报,找了一圈没有找到靠谱的接口,今天在中央气象台的官网查询某个城市找到了接口,先用postman试了一下居然可以使用,可以查询某个城市7天的天气预报等信息。但是查询编码是气象台自己的编码,在网上搜索了一下居然有这个编码。

    2023年04月14日
    浏览(52)
  • Winform中使用Websocket4Net实现Websocket客户端并定时存储接收数据到SQLite中

    SpringBoot+Vue整合WebSocket实现前后端消息推送: SpringBoot+Vue整合WebSocket实现前后端消息推送_websocket vue3.0 springboot 往客户端推送-CSDN博客 上面实现ws推送数据流程后,需要在windows上使用ws客户端定时记录收到的数据到文件中,这里 文件使用SQLite数据库进行存储。 Winform中操作Sql

    2024年02月22日
    浏览(49)
  • smiley-http-proxy-servlet 实现springboot 接口反向代理,站点代理,项目鉴权,安全的引入第三方项目服务

    2023-11-15 SmileSayBoot 开源,项目内扩展 实现了动态代理,可代理API接口/站点,若对你有用,请点个☆star 谢谢,能力有限,不喜勿喷。 背景: 项目初期 和硬件集成,实现了些功能服务,由于是局域网环境,安全问题当时都可以最小化无视。随着对接的服务越来越多,部分功能

    2024年02月12日
    浏览(54)
  • 【嵌入式资讯】Arm 通过引入 Keil MDK 版本 6 将嵌入式软件开发提升到一个新的水平

            随着物联网设备变得越来越智能,开发人员面临着日益增加的软件复杂性,这需要新的开发流程来创建优化的 ML 模型和高效的设备驱动程序。因此,我们为生态系统提供的软件开发平台和工具必须与我们的处理器路线图一起发展。Keil 微控制器开发套件 (Keil M

    2024年02月15日
    浏览(98)
  • asp.net core6 webapi 使用反射批量注入接口层和实现接口层的接口的类到ioc中

    IBLL接口层类库 BLL实现接口层类库 program中利用反射批量注入 在控制器中使用构造函数传参就可以调用已经注册的所有是是实现接口的类了的实列了

    2024年02月13日
    浏览(40)
  • .NET 6.0 中引入异步流(Async Streams)

    异步流(Async Streams):.NET 6.0 引入了异步流的概念,使得以异步方式产生和消费数据变得更加容易和高效。它可以通过 yield return 和 await foreach 语法进行操作,适用于处理大量数据或需要与慢速数据源交互的场景。 下面是一个使用异步流的简单示例: 在上述示例中, Genera

    2024年02月12日
    浏览(36)
  • .NET中有多少种定时器

    .NET中至少有6种定时器,每一种定时器都有它的用途和特点。根据定时器的应用场景,可以分为UI相关的定时器和UI无关的定时器。本文将简单介绍这6种定时器的基本用法和特点。 .NET中的UI定时器主要是WinForm、WPF以及WebForm中的定时器。分别为: System.Windows.Forms.Timer System.Win

    2024年02月05日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包