阅读本文你的收获
- 了解定时任务的使用场景
- 学会在ASP.NET Core 中使用托管服务
- 学会利用托管服务+Timer定时器实现定时任务
一、定时任务的应用场景
在系统开发过程中,很多时候我们不能对某一状态或某一行为实时进行人为监控,所以就希望系统能自动实现这一功能。这就需要写一些后台程序,它们可以在应用系统启动时,或者在某个时间点去触发启动,启动后就能自动、定期地在后台运行,这就是后台的定时任务。
定时任务的使用场景有:
- 定时给客户群发邮件
- 定时检查系统版本更新
- 定时从远程接口更新本地缓存数据,系统间数据同步
- 定时进行文件切割、临时文件删除。
- 定时更新数据看板的数据,如待办任务数量、订单数、每日销售额等
- 电商系统中定时检查未支付订单,自动关闭等。
二、ASP.NET Core中怎么做定时任务
在ASP.NET Core中,可以使用托管服务(Hosted Service)结合Timer计时器可以实现后台定时任务。这种方案比较适合简单的场景,对于任务的定时规则设置也很单一,也不用去监控任务的执行状态。本文主要探讨这种方案。
对于更加复杂的应用场景,也可以使用功能更加强大的任务调度框架,来实现定时任务。这样的框架有:
- QuartZ.NET
- HangFire
三、ASP.NET Core中的托管服务
在 ASP.NET Core 中,后台任务是作为托管服务(Hosted Service)实现的。 想要实现后台任务可以实现IHostedService接口或者直接继承BackgroundService抽象类。
- IHostedService接口是托管服务接口,在Microsoft.Extensions.Hosting命名空间下面,有两个接口方法:
- StartAsync(CancellationToken) :当应用程序主机准备好启动服务时触发该方法
- StopAsync(CancellationToken):当应用程序主机执行正常关闭时触发该方法。
- BackgroundService类是一个抽象类,这个类实现 IHostedService 接口,并将包含后台任务的具体代码逻辑。该抽象类有一个抽象方法:
- ExecuteAsync(CancellationToken): 子类需要重写该方法以执行具体的任务处理
3.1 编写一个托管服务
开发环境
平台版本是:.NET6
开发框架:ASP.NET Core WebApi
开发工具:Visual Studio 2022
-
定义一个后台服务类,注意实现IHostedService 接口或直接继承BackgroundService抽象类
csharp/// <summary> /// 自定义后台任务类 /// </summary> public class MyHostedService : BackgroundService { /// <summary> /// 执行任务 /// </summary> /// <param name="stoppingToken"></param> /// <returns></returns> protected override Task ExecuteAsync(CancellationToken stoppingToken) { int i = 1; //如果没停止,就一直执行 while (!stoppingToken.IsCancellationRequested) { DoWork(i++); } return Task.CompletedTask; } /// <summary> /// 打印输出一句话 /// </summary> /// <param name="i"></param> private void DoWork(int i) { Console.WriteLine("后台任务执行MyHostedService,第{0}次", i); } }
-
注册托管服务
csharp//.NET6中的Program.cs中注册后台任务 builder.Services.AddHostedService<MyHostedService>();
-
运行项目,就会看到后台任务不断被执行
四、Timer定时器+托管服务 实现简单定时任务
3.1. Timer计时器
System.Threading.Timer是一个非常常用的定时器类,是一个使用回调方法的计时器,而且由线程池线程服务,简单且对资源要求不高。
csharp
//以下演示如何使用Timer类
using System;
using System.Threading;
public class TestTimer
{
/// <summary>
/// 定时器
/// </summary>
private Timer _iTimer;
/// <summary>
/// 构造方法
/// </summary>
public TestTimer()
{
_iTimer = new Timer(Doing,null,TimeSpan.Zero, TimeSpan.FromMinutes(5));
}
/// <summary>
/// 执行具体定定时任务
/// </summary>
/// <param name="nObject"></param>
public void Doing(object nObject)
{
//具体代码。。
}
}
3.2. 实现每10分钟1次检查订单状态
-
创建后台服务类TimedHostedService,继承BackgroundService抽象基类,该抽象类实现了 IHostedService接口
csharpusing System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; namespace XfTechOAWeb.ApiServer.Background { /// <summary> /// 定时服务 /// </summary> public class TimedHostedService : BackgroundService { /// <summary> /// 执行任务 /// </summary> /// <param name="stoppingToken"></param> /// <returns></returns> protected override Task ExecuteAsync(CancellationToken stoppingToken) { return Task.CompletedTask; } } }
-
实现ExecuteAsync(CancellationToken stoppingToken)抽象方法,启动定时器
csharpusing System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; namespace XfTechOAWeb.ApiServer.Background { /// <summary> /// 定时服务 /// </summary> public class TimedHostedService : BackgroundService { private Timer _timer; //定时器 /// <summary> /// 执行任务 /// </summary> /// <param name="stoppingToken"></param> /// <returns></returns> protected override Task ExecuteAsync(CancellationToken stoppingToken) { //定时器创建后马上执行DoWork方法,每10秒钟执行一次 _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(10)); return Task.CompletedTask; } /// <summary> /// 定时器具体执行的方法 /// </summary> /// <param name="state"></param> private void DoWork(object state) { //做一些事情 } /// <summary> /// 释放资源 /// </summary> public override void Dispose() { _timer?.Dispose(); //如果_timer对象不为null,则销毁 base.Dispose(); } } }
-
在定时器执行的任务中,使用有作用域的服务,如使用订单服务。
csharpusing System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; //IServiceProvider接口的命名空间 using XfTechOAWeb.Application.Interfaces; //应用服务接口的命名空间 namespace XfTechOAWeb.ApiServer.Background { /// <summary> /// 定时服务 /// </summary> public class TimedHostedService : BackgroundService { private readonly IServiceProvider _serviceP; private Timer _timer; //定时器 /// <summary> /// 构造方法 /// </summary> /// <param name="scopeFactory"></param> public TimedHostedService(IServiceProvider serviceP) { _serviceP = serviceP; } /// <summary> /// 执行任务 /// </summary> /// <param name="stoppingToken"></param> /// <returns></returns> protected override Task ExecuteAsync(CancellationToken stoppingToken) { _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(10*60)); //定时器创建后马上执行,每10分钟执行一次 return Task.CompletedTask; } /// <summary> /// 定时器具体执行的方法 /// </summary> /// <param name="state"></param> private void DoWork(object state) { //创建服务作用域 using(var scope = _serviceP.CreateScope()) { //IOrderService 的实现代码 略 var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>(); //检查订单是否已支付,超过10分钟未支付订单自动关闭 orderService.CheckOrderPayment(); } } /// <summary> /// 释放资源 /// </summary> public override void Dispose() { _timer?.Dispose(); //如果_timer对象不为null,则销毁 base.Dispose(); } } }
在托管服务中使用服务的一种好方法是在需要时创建作用域。这允许将应用服务/数据库上下文等与设置它们的生命周期配置一起使用。
csharp
private readonly IServiceProvider _serviceP;
//创建服务作用域
using(var scope = _serviceP.CreateScope())
{
//...
}
-
在
IHostBuilder.ConfigureServices
(Program.cs) 中注册托管服务及有作用域的其他服务csharpbuilder.Services.AddHostedService<TimedHostedService>(); //注册托管服务--定时任务 builder.Services.AddScoped<IOrderService, OrderService>(); //注册订单服务--有作用域的服务
结语
本文提供了一个简单示例来演示如何使用IHostedService+Timer实现定时任务,并将其注册到ASP.NET Core应用程序中。它的优点是轻量级别,使用简单;如果是比较复杂的,批量的定时任务,并且需要对任务进行监听,还需要并发控制等场景,那么推荐你还是使用第三方定时任务组件,比如QuartZ等主流工具。
如果本文对你有帮助的话,请点赞+关注,或者转发给需要的朋友。