WebAPI-定时任务

WebAPI-定时任务

Hosted Services

IHostedService接口

IHostedService 是一个接口,它定义了两个方法:StartAsync(CancellationToken)StopAsync(CancellationToken)。这两个方法分别用于启动和停止后台服务。当你实现这个接口时,你需要自己处理所有的逻辑,包括如何开始、执行任务以及优雅地关闭服务

BackgroundService抽象类

BackgroundService 是一个抽象类,它实现了 IHostedService 接口,并且提供了一个默认的实现来简化开发过程。它引入了一个名为 ExecuteAsync(CancellationToken) 的虚方法,你可以在子类中重写这个方法来实现具体的业务逻辑。BackgroundService 会自动处理一些常见的任务,比如在应用程序关闭时正确地停止服务。

自动触发

BackgroundService 中定义的任务是自动触发的,无需手动触发。当你将一个实现了 IHostedService 接口(或继承自 BackgroundService)的服务注册到 ASP.NET Core 的依赖注入容器中时,ASP.NET Core 会在应用程序启动时自动调用该服务的 StartAsync 方法,并在应用程序关闭时调用 StopAsync 方法

program
// 注册定时任务类
builder.Services.AddHostedService<MyTask>();
MyTask
public class MyTask: BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // 执行你的任务逻辑
            Console.WriteLine("执行定时任务: " + DateTime.Now);

            // 等待一段时间再执行下一次
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }
    
    /// <summary>
    /// 如果存在需要释放的资源可以填写到这里
    /// </summary>
    public override void Dispose()
        {
            // 释放资源
            base.Dispose();
        }
}

Timer

在 .NET 中,System.Threading.Timer 类提供了一种简单的方式来执行定时任务。它允许你指定一个回调方法,在经过一段指定的时间后调用该方法。Timer 类非常适合用于需要定期或延时执行的任务

public Timer(
    TimerCallback callback,
    object state,
    int dueTime,
    int period
)
  • callback:一个 TimerCallback 委托,指向当定时器触发时要调用的方法。
  • state:传递给回调方法的任意对象。这个参数可以用来传递数据到回调方法中。
  • dueTime:定时器启动前的初始延迟时间(以毫秒为单位)。
  • period:定时器触发之间的间隔时间(以毫秒为单位)。如果设置为 -1 或 Timeout.Infinite,则定时器仅触发一次。
public class MyTask : BackgroundService
{
    private Timer _timer;

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 设置定时器,每30秒触发一次
        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(30));

        // 返回一个永远不会完成的任务(已经完成的任务),以保持服务运行
        // 替代了使用while循环来保持服务运行
        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        // 执行任务逻辑
        Console.WriteLine("执行定时任务: " + DateTime.Now);

        // 检查是否需要停止
        if (stoppingToken.IsCancellationRequested)
        {
            _timer.Dispose();
        }	
    }

    public override void Dispose()
    {
        // 释放资源
        _timer?.Dispose();
        base.Dispose();
    }
}

Cron表达式

cron表达式是一个字符串,通过crom表达式可以定义任务触发的时间

构成规则:分为6或者个域,由空格分开,每个域代表一个含义

分钟小时年(可为空)

注:周和日应该只有一个出现,而为空的时候填写

Cron - 在线Cron表达式生成器


NCrontab

如果想要实现指定规则去执行定时任务的话,仅仅通过Hosted Services是不行的

通过导入第三方库NCrontab,解析cron表达式,通过Timer来配置循环执行的定时任务,再通过Hosted Services来配置好后台任务

注: 因为Dal层是scope注入范围,而定时任务是单例,直接注入会出现异常,使用IServiceScopeFactory获取scope的Dal层对象

public class MyTask : BackgroundService
{
    private readonly CrontabSchedule _schedule;
    private Timer? _timer;
    private readonly ILogger<MyTask> _logger;

    public MyTask(ILogger<MyTask> logger)
    {
        _logger = logger;
        // 解析 CRON 表达式(使得NCrontab支持秒级定时)
        _schedule = CrontabSchedule.Parse("5/5 * * * * *", new CrontabSchedule.ParseOptions { IncludingSeconds = true });
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 获取下一次执行的时间,并设置定时器
        _timer = new Timer(DoWork, null, _schedule.GetNextOccurrence(DateTime.Now) - DateTime.Now, TimeSpan.FromMilliseconds(-1));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        // 执行任务
        Console.WriteLine("执行每日任务: " + DateTime.Now);
        
        // 重新计算下次执行时间,并重新设置定时器
        // 通过不断设置定时器的方式替代Timer的period参数来实现循环
        _timer.Change(_schedule.GetNextOccurrence(DateTime.Now) - DateTime.Now, TimeSpan.FromMilliseconds(-1));
    }

    public override void Dispose()
    {
        base.Dispose();
        _timer?.Dispose();
    }
}