.NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 (Timer 优化版) ...

打印 上一主题 下一主题

主题 906|帖子 906|积分 2718

在上个月写过一篇 .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 的文章,当时 CronSchedule 的实现是使用了,每个服务都独立进入到一个 while 循环中,进行定期扫描是否到了执行时间来实现的,但是那个逻辑有些问题,经过各位朋友的测试,发现当多个任务的时候存在一定概率不按照计划执行的情况。

感谢各位朋友的积极探讨,多交流一起进步。之前那个 while 循环的逻辑每循环一次 Task.Delay 1000 毫秒,无限循环,多个任务的时候还会同时有多个循环任务,确实不够好。
所以决定重构 CronSchedule 的实现,采用全局使用一个 Timer 的形式,每隔 1秒钟扫描一次任务队列看看是否有需要执行的任务,整体的实现思路还是之前的,如果没有看过之前那篇文章的建议先看一下,本片主要针对调整部分进行说明  .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 ,主要调整了 CronSchedule.cs
  1. using Common;
  2. using System.Reflection;
  3. namespace TaskService.Libraries
  4. {
  5.     public class CronSchedule
  6.     {
  7.         private static List<ScheduleInfo> scheduleList = new();
  8.         private static Timer mainTimer;
  9.         public static void Builder(object context)
  10.         {
  11.             var taskList = context.GetType().GetMethods().Where(t => t.GetCustomAttributes(typeof(CronScheduleAttribute), false).Length > 0).ToList();
  12.             foreach (var action in taskList)
  13.             {
  14.                 string cron = action.CustomAttributes.Where(t => t.AttributeType == typeof(CronScheduleAttribute)).FirstOrDefault()!.NamedArguments.Where(t => t.MemberName == "Cron" && t.TypedValue.Value != null).Select(t => t.TypedValue.Value!.ToString()).FirstOrDefault()!;
  15.                 scheduleList.Add(new ScheduleInfo
  16.                 {
  17.                     CronExpression = cron,
  18.                     Action = action,
  19.                     Context = context
  20.                 });
  21.             }
  22.             if (mainTimer == default)
  23.             {
  24.                 mainTimer = new(Run, null, 0, 1000);
  25.             }
  26.         }
  27.         private static void Run(object? state)
  28.         {
  29.             var nowTime = DateTime.Parse(DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"));
  30.             foreach (var item in scheduleList)
  31.             {
  32.                 if (item.LastTime != null)
  33.                 {
  34.                     var nextTime = DateTime.Parse(CronHelper.GetNextOccurrence(item.CronExpression, item.LastTime.Value).ToString("yyyy-MM-dd HH:mm:ss"));
  35.                     if (nextTime == nowTime)
  36.                     {
  37.                         item.LastTime = DateTimeOffset.Now;
  38.                         _ = Task.Run(() =>
  39.                         {
  40.                             item.Action.Invoke(item.Context, null);
  41.                         });
  42.                     }
  43.                 }
  44.                 else
  45.                 {
  46.                     item.LastTime = DateTimeOffset.Now.AddSeconds(5);
  47.                 }
  48.             }
  49.         }
  50.         private class ScheduleInfo
  51.         {
  52.             public string CronExpression { get; set; }
  53.             public MethodInfo Action { get; set; }
  54.             public object Context { get; set; }
  55.             public DateTimeOffset? LastTime { get; set; }
  56.         }
  57.     }
  58.     [AttributeUsage(AttributeTargets.Method)]
  59.     public class CronScheduleAttribute : Attribute
  60.     {
  61.         public string Cron { get; set; }
  62.     }
  63. }
复制代码
这里的逻辑改为了注入任务时将 mainTimer 实例化启动,每一秒钟执行1次 Run方法,Run 方法内部用于 循环检测 scheduleList 中的任务,如果时间符合,则启动一个 Task 去执行对应的 Action,这样全局不管注册多少个服务,也只有一个 Timer 在循环运行,相对之前的 CronSchedule 实现相对更好一点。
使用的时候方法基本没怎么改,只是调整了CronSchedule.Builder 的调用 代码如下:
  1. using DistributedLock;
  2. using Repository.Database;
  3. using TaskService.Libraries;
  4. namespace TaskService.Tasks
  5. {
  6.     public class DemoTask : BackgroundService
  7.     {
  8.         private readonly IServiceProvider serviceProvider;
  9.         private readonly ILogger logger;
  10.         public DemoTask(IServiceProvider serviceProvider, ILogger<DemoTask> logger)
  11.         {
  12.             this.serviceProvider = serviceProvider;
  13.             this.logger = logger;
  14.         }
  15.         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  16.         {
  17.             CronSchedule.Builder(this);
  18.             await Task.Delay(-1, stoppingToken);
  19.         }
  20.         [CronSchedule(Cron = "0/1 * * * * ?")]
  21.         public void ClearLog()
  22.         {
  23.             try
  24.             {
  25.                 using var scope = serviceProvider.CreateScope();
  26.                 var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
  27.                 //省略业务代码
  28.                 Console.WriteLine("ClearLog:" + DateTime.Now);
  29.             }
  30.             catch (Exception ex)
  31.             {
  32.                 logger.LogError(ex, "DemoTask.ClearLog");
  33.             }
  34.         }
  35.         [CronSchedule(Cron = "0/5 * * * * ?")]
  36.         public void ClearCache()
  37.         {
  38.             try
  39.             {
  40.                 using var scope = serviceProvider.CreateScope();
  41.                 var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
  42.                 var distLock = scope.ServiceProvider.GetRequiredService<IDistributedLock>();
  43.                 //省略业务代码
  44.                 Console.WriteLine("ClearCache:" + DateTime.Now);
  45.             }
  46.             catch (Exception ex)
  47.             {
  48.                 logger.LogError(ex, "DemoTask.ClearCache");
  49.             }
  50.         }
  51.     }
  52. }
复制代码
然后启动我们的项目就可以看到如下的运行效果:

最上面连着两个 16:25:53 并不是重复调用了,只是因为这个任务配置的是 1秒钟执行1次,第一次启动任务的时候执行的较为耗时,导致第一次执行和第二次执行进入到方法中的时间差太短了,这个只在第一次产生,对后续的执行计划没有影响。
至此 .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 (Timer 优化版) 就讲解完了,有任何不明白的,可以在文章下面评论或者私信我,欢迎大家积极的讨论交流,有兴趣的朋友可以关注我目前在维护的一个 .NET 基础框架项目,项目地址如下https://github.com/berkerdong/NetEngine.githttps://gitee.com/berkerdong/NetEngine.git
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

滴水恩情

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表