翼度科技»论坛 编程开发 .net 查看内容

.NET 8 强大功能 IHostedService 与 BackgroundService 实战

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
前言

在.NET 8中,IHostedService 和 BackgroundService 两个核心接口的引入,增强了项目开发中处理定时任务的能力。这两个接口不仅简化了定时任务、后台处理作业以及定期维护任务的实现过程,还提升了在ASP.NET Core 或任何基于.NET的宿主应用程序中的集成与管理效率。
IHostedService接口提供了一个基本的框架,允许自定义后台服务的启动和停止逻辑。通过实现该接口,可以灵活地控制服务的生命周期,确保任务在应用程序启动时自动运行,并在应用程序关闭时结束。
而 BackgroundService 类则是对 IHostedService 接口的进一步封装,它专为需要长时间运行的任务而设计。
通过继承 BackgroundService并重写其 ExecuteAsync方法,可以轻松地实现复杂的后台逻辑,如循环执行的任务、基于时间间隔的操作等。这种设计模式让代码的可读性和可维护性变的更好。
利用这些功能,可以快速构建出高效、可靠的定时任务系统,用于执行诸如消息推送、数据更新、定时发布等关键业务操作。这些任务可以在不影响应用程序主流程的情况下独立运行,从而提高了整个系统的性能和稳定性。
介绍

.NET 中的后台服务允许在后台独立于主应用程序线程运行任务。这对于需要连续或定期运行而不阻塞主应用程序流的任务至关重要。
IHostedService
IHostedService 是一个简单的接口,用于实现后台服务。当需要实现自定义的托管服务时,可以通过实现这个接口来创建。
该接口定义了两个方法:StartAsync(CancellationToken cancellationToken) 和 StopAsync(CancellationToken cancellationToken),分别用于启动和停止服务。
BackgroundService
BackgroundService 是一个抽象类,它继承自 IHostedService 并提供了更简洁的方式来编写后台服务。它通常用于需要长时间运行的任务,如监听器、工作队列处理器等。通过重写 ExecuteAsync(CancellationToken stoppingToken)方法,可以在其中编写任务的逻辑。ExecuteAsync方法会循环在后台执行,直到服务停止。
IHostedService 示例

1、注册服务

Program.cs 中添加配置,.NET 5 及以下在需要在 Startup.cs 注册服务。
  1. // .NET 8
  2. using ManageCore.Api;
  3. var builder = WebApplication.CreateBuilder(args);
  4. builder.Services.AddControllers();
  5. builder.Services.AddEndpointsApiExplorer();
  6. builder.Services.AddSwaggerGen();
  7. builder.Services.AddHostedService<DemoHostedService>();
  8. var app = builder.Build();
复制代码
2、创建服务接口

创建一个类,该类继承 IHostedService 接口,并实现该接口成员.
在不需要定时执行任务的时候,也可以在这里进行应用启动后的操作,例如创建 RabbitMQ 连接
  1. using Microsoft.Extensions.Hosting;
  2. namespace ManageCore.Api
  3. {
  4.     public class DemoHostedService : IHostedService, IDisposable
  5.     {
  6.         private Timer? _timer;
  7.         public Task StartAsync(CancellationToken cancellationToken)
  8.         {
  9.             _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
  10.             return Task.CompletedTask;
  11.         }
  12.         private void DoWork(object? state)
  13.         {
  14.             Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
  15.         }
  16.         public Task StopAsync(CancellationToken cancellationToken)
  17.         {
  18.             Console.WriteLine("StopAsync");
  19.             return Task.CompletedTask;
  20.         }
  21.         public void Dispose()
  22.         {
  23.             _timer?.Dispose();
  24.         }
  25.     }
  26. }
复制代码
上面的Demo代码非常简单,应用在运行后,会去执行 StartAsync 函数,应用关闭执行 StopAsync,由于这里使用的定时器,所以每过5秒都会执行一次 DoWork 函数。
 3、运行效果


4、IHostedService 说明

注意:定时是不等待任务执行完成,只要时间一到,就会调用 DoWork 函数,所以适合一些简单、特定的场景。
以下为官方文档对 IHostedService 接口 的说明
IHostedService 接口为主机托管的对象定义了两种方法:

  • StartAsync(CancellationToken)
  • StopAsync(CancellationToken)
StartAsync(CancellationToken) 包含用于启动后台任务的逻辑。 在以下操作之前调用 `StartAsync`:已配置应用的请求处理管道。已启动服务器且已触发 IApplicationLifetime.ApplicationStarted。
StartAsync应仅限于短期任务,因为托管服务是按顺序运行的,在 StartAsync 运行完成之前不会启动其他服务。
StopAsync(CancellationToken) 在主机执行正常关闭时触发。 StopAsync`包含结束后台任务的逻辑。 实现 IDisposable 和终结器(析构函数)以处置任何非托管资源。
默认情况下,取消令牌会有五秒超时,以指示关闭进程不再正常。 在令牌上请求取消时:

  • 应中止应用正在执行的任何剩余后台操作。
  • StopAsync 中调用的任何方法都应及时返回。
但是,在请求取消后,将不会放弃任务,调用方会等待所有任务完成。
如果应用意外关闭(例如,应用的进程失败),则可能不会调用 StopAsync。 因此,在 StopAsync 中执行的任何方法或操作都可能不会发生。
若要延长默认值为 5 秒的关闭超时值,请设置:

  • ShutdownTimeout(当使用通用主机时)
  • 使用 Web 主机时为关闭超时值主机配置设置
托管服务在应用启动时激活一次,在应用关闭时正常关闭。 如果在执行后台任务期间引发错误,即使未调用 StopAsync,也应调用 Dispose。
BackgroundService 示例

1、注册服务

首先,同样需要在配置中注册服务接口。
  1. using ManageCore.Api;
  2. var builder = WebApplication.CreateBuilder(args);
  3. builder.Services.AddControllers();
  4. builder.Services.AddEndpointsApiExplorer();
  5. builder.Services.AddSwaggerGen();
  6. builder.Services.AddScoped<IDemoTaskWorkService, DemoTaskWorkService>();
复制代码
 2、BackgroundService 源码

查看 BackgroundService 的源码,帮助我们理解BackgroundService 实现原理。BackgroundService 是 IHostedService的一个简单实现,内部 IHostedService 的 StartAsync 调用了 ExecuteAsync,本质上就是使用了 IHostedService。
  1. public abstract class BackgroundService : IHostedService, IDisposable
  2. {
  3.     private Task _executingTask;
  4.     private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
  5.     /// <summary>
  6.     /// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents
  7.     /// the lifetime of the long running operation(s) being performed.
  8.     /// /// </summary>
  9.     /// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
  10.     /// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
  11.     protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
  12.     /// <summary>
  13.     /// Triggered when the application host is ready to start the service.
  14.     /// </summary>
  15.     /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
  16.     public virtual Task StartAsync(CancellationToken cancellationToken)
  17.     {
  18.         // Store the task we're executing
  19.         _executingTask = ExecuteAsync(_stoppingCts.Token);
  20.         // If the task is completed then return it, this will bubble cancellation and failure to the caller
  21.         if (_executingTask.IsCompleted)
  22.         {
  23.             return _executingTask;
  24.         }
  25.         // Otherwise it's running
  26.         return Task.CompletedTask;
  27.     }
  28.     /// <summary>
  29.     /// Triggered when the application host is performing a graceful shutdown.
  30.     /// </summary>
  31.     /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
  32.     public virtual async Task StopAsync(CancellationToken cancellationToken)
  33.     {
  34.         // Stop called without start
  35.         if (_executingTask == null)
  36.         {
  37.             return;
  38.         }
  39.         try
  40.         {
  41.             // Signal cancellation to the executing method
  42.             _stoppingCts.Cancel();
  43.         }
  44.         finally
  45.         {
  46.             // Wait until the task completes or the stop token triggers
  47.             await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
  48.         }
  49.     }
  50.     public virtual void Dispose()
  51.     {
  52.         _stoppingCts.Cancel();
  53.     }
  54. }
复制代码
3、创建服务接口

创建一个服务接口,定义需要实现的任务,以及对应的实现,如果需要执行异步方法,记得加上 await,不然任务将不会等待执行结果,直接进行下一个任务。
  1. namespace ManageCore.Api
  2. {
  3.     public interface IDemoTaskWorkService
  4.     {
  5.         /// <summary>
  6.         /// 测试任务
  7.         /// </summary>
  8.         /// <param name="stoppingToken"></param>
  9.         /// <returns></returns>
  10.         Task TaskWorkAsync(CancellationToken stoppingToken);
  11.     }
  12. }
复制代码
  1. public class DemoTaskWorkService : IDemoTaskWorkService
  2. {
  3.       /// <summary>
  4.       /// 任务执行
  5.       /// </summary>
  6.       /// <param name="stoppingToken"></param>
  7.       /// <returns></returns>
  8.       public async Task TaskWorkAsync(CancellationToken stoppingToken)
  9.       {
  10.           while (!stoppingToken.IsCancellationRequested)
  11.           {
  12.               //执行任务
  13.               Console.WriteLine($"{DateTime.Now}");
  14.               //周期性任务,于上次任务执行完成后,等待5秒,执行下一次任务
  15.               await Task.Delay(500);
  16.           }
  17.       }
  18. }
复制代码
创建后台服务类,继承基类 BackgroundService,这里需要注意的是,要在 BackgroundService 中使用有作用域的服务,请创建作用域, 默认情况下,不会为托管服务创建作用域,得自己管理服务的生命周期,切记!于构造函数中注入 IServiceProvider即可。
  1. namespace ManageCore.Api
  2. {
  3.     public class DemoBackgroundService : BackgroundService
  4.     {
  5.         private readonly IServiceProvider _services;
  6.         public DemoBackgroundService(IServiceProvider services)
  7.         {
  8.             _services = services;
  9.         }
  10.         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  11.         {
  12.             using var scope = _services.CreateScope();
  13.             var taskWorkService = scope.ServiceProvider.GetRequiredService<IDemoTaskWorkService>();
  14.             await taskWorkService.TaskWorkAsync(stoppingToken);
  15.         }
  16.     }
  17. }
复制代码
DemoBackgroundService类也是需要注册的,注册方式与 IHostedService 接口的方式一样
  1. builder.Services.AddHostedService<DemoBackgroundService>();
复制代码
4、运行效果


5、BackgroundService 说明

BackgroundService 是用于实现长时间运行的 IHostedService 的基类。
调用 ExecuteAsync(CancellationToken) 来运行后台服务。 实现返回一个 Task,其表示后台服务的整个生存期。
在 ExecuteAsync 变为异步(例如通过调用 await)之前,不会启动任何其他服务。 避免在 ExecuteAsync 中执行长时间的阻塞初始化工作。
StopAsync(CancellationToken) 中的主机块等待完成 ExecuteAsync。
调用 IHostedService.StopAsync 时,将触发取消令牌。 当激发取消令牌以便正常关闭服务时,ExecuteAsync 的实现应立即完成。 否则,服务将在关闭超时后不正常关闭。
StartAsync 应仅限于短期任务,因为托管服务是按顺序运行的,在 StartAsync 运行完成之前不会启动其他服务。
长期任务应放置在 ExecuteAsync 中。
IHostedService 和 BackgroundService 区别

抽象级别


  • IHostedService:需要手动实现启动和停止逻辑。
  • BackgroundService:通过提供具有要重写的单个方法的基类来简化实现。
使用案例


  • IHostedService:适用于需要对服务生命周期进行精细控制的更复杂的方案。
  • BackgroundService:非常适合受益于减少样板代码的更简单、长时间运行的任务。
总结

总之.NET 8 中的 IHostedService 和 BackgroundService 提供了强大的工具集,使定时任务、后台处理以及定期维护等功能的实现变得更加直接、高效和灵活。无论是构建复杂的企业级应用还是简单的服务应用,这两个组件都能提供稳定且高效的解决方案。
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!


来源:https://www.cnblogs.com/1312mn/p/18340210
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具