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

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

所以决定重构 CronSchedule 的实现,采用全局使用一个 Timer 的形式,每隔 1秒钟扫描一次任务队列看看是否有需要执行的任务,主要调整了 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 基础框架项目,项目地址如下

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

  1. .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件

    常用的定时任务组件有 Quartz.Net 和 Hangfire 两种,这两种是使用人数比较多的定时任务组件,个人以前也是使用的 Hangfire ,慢慢的发现自己想要的其实只是一个能够根据 Cron ...

  2. .NET定时任务执行管理器开源组件–FluentScheduler

    在日常项目里通常会遇到定时执行任务的需求,也就是定时器..NET Framework里关于定时器的类有3个,分别是System.Windows.Forms.Timer.System.Timers.Ti ...

  3. linux ,cron定时任务 备份mysql数据库

    cron 定时任务执行备份脚本文件 backup.sh #!/bin/bash USER="root" PASSWORD="xxxxx" DATABASE=&q ...

  4. Go cron定时任务的用法

    cron是什么 cron的意思就是:计划任务,说白了就是定时任务.我和系统约个时间,你在几点几分几秒或者每隔几分钟跑一个任务(job),就那么简单. cron表达式 cron表达式是一个好东西,这个东 ...

  5. spring cron 定时任务

    文章首发于个人博客:https://yeyouluo.github.io 0 预备知识:cron表达式 见 <5 参考>一节. 1 环境 eclipse mars2 + Maven3.3. ...

  6. Spring task定时任务执行一段时间后莫名其妙停止的问题

    前因: 我写了一个小项目,主要功能是用Spring task定时任务每天定时给用户发送邮件.执行了几个月一直没有问题,前几天,莫名其妙的突然不再发送邮件了. 只好花费一些时间来查看到底是什么原因造成的 ...

  7. cron定时任务介绍

    什么是cron? Cron是linux系统中用来定期执行或指定程序任务的一种服务或软件.与它相关的有两个工具:crond 和 crontab.crond 就是 cron 在系统内的宿主程序,cront ...

  8. spring task 实现定时执行(补充:解决定时任务执行2次问题)

    首先在spring-mvc.xml配置头文件引入: xmlns:task="http://www.springframework.org/schema/task" 其次引入task ...

  9. linux之cron定时任务介绍

    前言 linux系统有一个专门用来管理定时任务的进程cron,一般是设置成开机自启动的,通过添加任务可以让服务器定时执行某些任务. cron介绍 linux系统有一个专门用来管理定时任务的进程cron ...

随机推荐

  1. archlinux-小米pro15_2020款使用archlinux(MX350显卡驱动安装)

    1.官网下载archlinux ISO镜像 https://archlinux.org/download/   使用磁力链接下载 2.使用软碟通将镜像写入U盘,制作成U盘启动盘 3.进入BIOS 关掉 ...

  2. ssh-修改ssh服务端口号

    在启动selinux的状态下修改ssh的端口号 0.启动selinux     setenforce 1     getenforce 1.修改sshd_config的Port为2234 查看2234 ...

  3. ​​​​​​​ARCGIS API for Python进行城市区域提取

    ​ArcGIS API for Python主要用于Web端的扩展和开发,提供简单易用.功能强大的Python库,以及大数据分析能力,可轻松实现实时数据.栅格数据.空间数据等多源数据的接入和GIS分析 ...

  4. 时空图神经网路:STGNNs

    STGNNs:SPATIAL–TEMPORAL GRAPH NEURAL NETWORKS 许多实际应用中的图在图结构和图输入方面都是动态的.STGNNs在捕获图的动态性方面占有重要地位. 这类方法的 ...

  5. HashSet存储自定义类型元素和LinkedHashSet集合

    HashSet集合存储自定义类型元素 HashSet存储自定义类型元素 set集合报错元素唯一: ~存储的元素(String,Integer,-Student,Person-)必须重写hashCode ...

  6. Redis 渐进集群介绍

    redis 凭借着强大的功能和可靠的稳定性,应用场景越来越广.逐渐成为软件开发工程师必备的技能之一. 本篇文章,暂不做基本功能的介绍.直接教大家如何部署redis集群. 集群演进主要分为2部分. 一. ...

  7. 常用Linux音译

    su:Swith user 切换用户,切换到root用户 cat: Concatenate 串联 uname: Unix name 系统名称 df: Disk free 空余硬盘 du: Disk u ...

  8. 浏览器js调试

    经常忘记,还是需要记录一下啊 右键,检查元素 在元素DOM节点右击,复制CSS选择器 function sleep (time) { return new Promise((resolve) => ...

  9. B.E.M 规范

    BEM文档 BEM: A New Front-End Methodology 如何看待 CSS 中 BEM 的命名方式? Battling BEM CSS: 10 Common Problems An ...

  10. 膜 社论(egg drop)

    题面 \(n\) 楼 \(m\) 个鸡蛋,从 \(k\) 楼及以上扔下去会碎,不能再测试 . 问至少需要扔几次确定 \(k\) . \(n\le 10^{18}\),\(m\le 64\) . 题解 ...