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

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

时间:2022-09-06 16:44:06浏览次数:99  
标签:CronSchedule Timer Cron item var using NET public

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

感谢各位朋友的积极探讨,多交流一起进步。之前那个 while 循环的逻辑每循环一次 Task.Delay 1000 毫秒,无限循环,多个任务的时候还会同时有多个循环任务,确实不够好。

所以决定重构 CronSchedule 的实现,采用全局使用一个 Timer 的形式,每隔 1秒钟扫描一次任务队列看看是否有需要执行的任务,整体的实现思路还是之前的,如果没有看过之前那篇文章的建议先看一下,本片主要针对调整部分进行说明  .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 ,主要调整了 CronSchedule.cs

using Common;
using System.Reflection;

namespace TaskService.Libraries
{
    public class CronSchedule
    {
        private static List<ScheduleInfo> scheduleList = new();
        private static Timer mainTimer;

        public static void Builder(object context)
        {
            var taskList = context.GetType().GetMethods().Where(t => t.GetCustomAttributes(typeof(CronScheduleAttribute), false).Length > 0).ToList();

            foreach (var action in taskList)
            {
                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()!;

                scheduleList.Add(new ScheduleInfo
                {
                    CronExpression = cron,
                    Action = action,
                    Context = context
                });
            }

            if (mainTimer == default)
            {
                mainTimer = new(Run, null, 0, 1000);
            }
        }


        private static void Run(object? state)
        {
            var nowTime = DateTime.Parse(DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"));

            foreach (var item in scheduleList)
            {
                if (item.LastTime != null)
                {
                    var nextTime = DateTime.Parse(CronHelper.GetNextOccurrence(item.CronExpression, item.LastTime.Value).ToString("yyyy-MM-dd HH:mm:ss"));

                    if (nextTime == nowTime)
                    {
                        item.LastTime = DateTimeOffset.Now;

                        _ = Task.Run(() =>
                        {
                            item.Action.Invoke(item.Context, null);
                        });
                    }
                }
                else
                {
                    item.LastTime = DateTimeOffset.Now.AddSeconds(5);
                }
            }
        }


        private class ScheduleInfo
        {
            public string CronExpression { get; set; }

            public MethodInfo Action { get; set; }

            public object Context { get; set; }

            public DateTimeOffset? LastTime { get; set; }
        }
    }

    [AttributeUsage(AttributeTargets.Method)]
    public class CronScheduleAttribute : Attribute
    {
        public string Cron { get; set; }
    }

}

这里的逻辑改为了注入任务时将 mainTimer 实例化启动,每一秒钟执行1次 Run方法,Run 方法内部用于 循环检测 scheduleList 中的任务,如果时间符合,则启动一个 Task 去执行对应的 Action,这样全局不管注册多少个服务,也只有一个 Timer 在循环运行,相对之前的 CronSchedule 实现相对更好一点。

使用的时候方法基本没怎么改,只是调整了CronSchedule.Builder 的调用 代码如下:

using DistributedLock;
using Repository.Database;
using TaskService.Libraries;

namespace TaskService.Tasks
{
    public class DemoTask : BackgroundService
    {

        private readonly IServiceProvider serviceProvider;
        private readonly ILogger logger;



        public DemoTask(IServiceProvider serviceProvider, ILogger<DemoTask> logger)
        {
            this.serviceProvider = serviceProvider;
            this.logger = logger;
        }


        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            CronSchedule.Builder(this);

            await Task.Delay(-1, stoppingToken);
        }



        [CronSchedule(Cron = "0/1 * * * * ?")]
        public void ClearLog()
        {
            try
            {
                using var scope = serviceProvider.CreateScope();
                var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();

                //省略业务代码
                Console.WriteLine("ClearLog:" + DateTime.Now);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "DemoTask.ClearLog");
            }
        }



        [CronSchedule(Cron = "0/5 * * * * ?")]
        public void ClearCache()
        {
            try
            {
                using var scope = serviceProvider.CreateScope();
                var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
                var distLock = scope.ServiceProvider.GetRequiredService<IDistributedLock>();

                //省略业务代码
                Console.WriteLine("ClearCache:" + DateTime.Now);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "DemoTask.ClearCache");
            }
        }

    }
}

然后启动我们的项目就可以看到如下的运行效果:

最上面连着两个 16:25:53 并不是重复调用了,只是因为这个任务配置的是 1秒钟执行1次,第一次启动任务的时候执行的较为耗时,导致第一次执行和第二次执行进入到方法中的时间差太短了,这个只在第一次产生,对后续的执行计划没有影响。

至此 .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 (Timer 优化版) 就讲解完了,有任何不明白的,可以在文章下面评论或者私信我,欢迎大家积极的讨论交流,有兴趣的朋友可以关注我目前在维护的一个 .NET 基础框架项目,项目地址如下 https://github.com/berkerdong/NetEngine.git https://gitee.com/berkerdong/NetEngine.git

标签:CronSchedule,Timer,Cron,item,var,using,NET,public
From: https://www.cnblogs.com/berkerdong/p/16662359.html

相关文章

  • .net core 利用qq发送邮件
    1、进入qq邮箱首页,点击设置   2、点击 账户 选项卡,划到大概中间部分,有一个服务选项区。经实验开启前两个就可以了。 3、点击下方的 生成授权码,在开启服务之......
  • ASP.NET Core 01基础知识概述
    Program.cs使用Web模板创建的ASP.NETCore应用包含Program.cs文件中的应用程序启动代码。Program.cs文件中包含:已配置应用所需的服务。应用的请求处理管道定义......
  • Netty+WebSocket整合STOMP协议
    1.STOMP协议简介常用的WebSocket协议定义了两种传输信息类型:文本信息和二进制信息。类型虽然被确定,但是他们的传输体是没有规定的,也就是说传输体可以自定义成什么样的数据......
  • netlify跳转
    <!DOCTYPEhtml><head><title>RedirectingtoNetlify</title><script>letgitUrl=window.top.location.hash.replace("#","")if(gitUrl.endsWith("/"))g......
  • Asp.net中引入AspNetPager.dll进行数据分页
    1、在AspNetPager.dl开发者官网【AspNetPager下载(webdiyer.com)】进行下载,或者直接百度搜索下载2、右键单机选中自己项目,打开。3、点击打开bin文件4、将AspNetPage......
  • Asp.Net的各种传值方式
    1.QueryString是一种非常简单的传值方式,他可以将传送的值显示在浏览器的地址栏中。如果是传递一个或多个安全性要求不高或是结构简单的数值时,可以使用这个方法。但是对于传......
  • Linux 安装telnet
    一、安装telnet1、首先我们检测telnet-server的rpm包是否安装[root@localhost~]#rpm-qatelnet-server若无输入内容,则表示没有安装。linux的telnet-server.rpm默认......
  • NET5配合vue3图片上传
    NET5配合vue3图片上传后端函数///<summary>///上传文件///</summary>///<paramname="files">文件流</param>///<ret......
  • ASP.NET Core 6框架揭秘实例演示[35]:利用Session保留语境
    客户端和服务器基于HTTP的消息交换就好比两个完全没有记忆能力的人在交流,每次单一的HTTP事务体现为一次“一问一答”的对话。单一的对话毫无意义,在在同一语境下针对某个主......
  • k8s--CronJob(CJ) 控制器
    CronJob介绍CronJob控制器以Job控制器资源为其管控对象,并借助它管理pod资源对象,job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类......