From 4bb1064352b14bc88c950efff3896bf222ebf1c2 Mon Sep 17 00:00:00 2001 From: Falcon <12919280+falconfly@user.noreply.gitee.com> Date: Wed, 19 Feb 2025 15:57:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5TimedTask=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IServiceCollectionExtend.cs | 22 +++ .../TimedBackgroundTask/TimedTask.cs | 135 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 Falcon.SugarApi/TimedBackgroundTask/IServiceCollectionExtend.cs create mode 100644 Falcon.SugarApi/TimedBackgroundTask/TimedTask.cs diff --git a/Falcon.SugarApi/TimedBackgroundTask/IServiceCollectionExtend.cs b/Falcon.SugarApi/TimedBackgroundTask/IServiceCollectionExtend.cs new file mode 100644 index 0000000..03e1620 --- /dev/null +++ b/Falcon.SugarApi/TimedBackgroundTask/IServiceCollectionExtend.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Falcon.SugarApi.TimedBackgroundTask +{ + /// + /// 服务集合扩展方法 + /// + public static class IServiceCollectionExtend + { + /// + /// 向HostService中注册TimedBackgroundTask服务 + /// + /// TimedBackgroundTask的具体类型 + /// 服务集合 + /// 服务集合 + public static IServiceCollection AddTimedBackgroundTask(this IServiceCollection services) + where T : TimedTask { + services.AddHostedService(); + return services; + } + } +} diff --git a/Falcon.SugarApi/TimedBackgroundTask/TimedTask.cs b/Falcon.SugarApi/TimedBackgroundTask/TimedTask.cs new file mode 100644 index 0000000..fadd64b --- /dev/null +++ b/Falcon.SugarApi/TimedBackgroundTask/TimedTask.cs @@ -0,0 +1,135 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Falcon.SugarApi.TimedBackgroundTask +{ + /// + /// 由定时器驱动的后台任务 + /// + public class TimedTask:BackgroundService, IDisposable + { + /// + /// 日志记录器 + /// + protected readonly ILogger? Logger; + /// + /// 心跳计时器 + /// + private readonly PeriodicTimer _timer; + + /// + /// 运行一次任务 + /// + /// 退出信号 + /// 如果继续执行返回True,否则False + public virtual async Task Run(CancellationToken cancellationToken) => await Task.FromResult(true); + + /// + /// Timer心跳 + /// + public virtual int Heartbeat { get; set; } = 1; + + /// + /// 执行计划的Cron串 + /// + public virtual string CronSchedule { get; set; } = "30 * * * * *"; + + /// + /// 获取下次执行任务的计划 + /// + public CronExpression Schedule { get; private set; } + + /// + /// 任务正在运行 + /// + private bool _isRunning = false; + /// + /// 下次执行时间 + /// + public DateTime NextTickTime { get; set; } = DateTime.MinValue; + + /// + /// 后台任务开始 + /// + protected virtual void OnStart(TimedTask t,CancellationToken stoppingToken) { } + /// + /// 后台任务停止 + /// + protected virtual void OnStop(TimedTask t,CancellationToken stoppingToken) { } + /// + /// 完成一次执行 + /// + protected virtual void OnCompleted(TimedTask t,CancellationToken stoppingToken) { } + /// + /// 执行中发生未处理异常 + /// + protected virtual bool OnException(TimedTask t,Exception ex,CancellationToken stoppingToken) { + return false; + } + /// + /// 系统服务 + /// + public IServiceProvider Service { get; set; } + /// + /// 资源锁 + /// + private static object _lock = new object(); + + /// + /// 构造Timed后台任务 + /// + /// 服务提供器 + public TimedTask(IServiceProvider service) { + this.Service = service; + this.Logger = service.GetService(typeof(ILogger<>).MakeGenericType(GetType())) as ILogger; + _timer = new PeriodicTimer(TimeSpan.FromSeconds(this.Heartbeat)); + this.Schedule = new CronExpression(this.CronSchedule); + } + + /// + /// 由系统调度执行任务 + /// + /// 退出信号 + /// 无返回 + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + this.OnStart(this,stoppingToken); + while(await _timer.WaitForNextTickAsync(stoppingToken)) { + lock(_lock) { + if(stoppingToken.IsCancellationRequested) { + break; + } + if(this._isRunning || DateTime.Now < this.NextTickTime) { + continue; + } + this._isRunning = true; + } + try { + var goOn = await this.Run(stoppingToken); + this.OnCompleted(this,stoppingToken); + if(goOn) { + continue; + } + } + catch(Exception ex) { + this.OnException(this,ex,stoppingToken); + } + SetNextTick(); + this._isRunning = false; + } + this.OnStop(this,stoppingToken); + } + + private void SetNextTick(string? cronSchedule = null) { + var s = cronSchedule == null ? this.Schedule : new CronExpression(cronSchedule); + this.NextTickTime = s.GetNextOccurrence(DateTime.Now); + } + + public override void Dispose() { + this._timer?.Dispose(); + base.Dispose(); + } + } +}