.NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 (Timer 优化版)
在上个月写过一篇 .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 纯原生实现 Cron 定时任务执行,未依赖第三方组件
常用的定时任务组件有 Quartz.Net 和 Hangfire 两种,这两种是使用人数比较多的定时任务组件,个人以前也是使用的 Hangfire ,慢慢的发现自己想要的其实只是一个能够根据 Cron ...
- .NET定时任务执行管理器开源组件–FluentScheduler
在日常项目里通常会遇到定时执行任务的需求,也就是定时器..NET Framework里关于定时器的类有3个,分别是System.Windows.Forms.Timer.System.Timers.Ti ...
- linux ,cron定时任务 备份mysql数据库
cron 定时任务执行备份脚本文件 backup.sh #!/bin/bash USER="root" PASSWORD="xxxxx" DATABASE=&q ...
- Go cron定时任务的用法
cron是什么 cron的意思就是:计划任务,说白了就是定时任务.我和系统约个时间,你在几点几分几秒或者每隔几分钟跑一个任务(job),就那么简单. cron表达式 cron表达式是一个好东西,这个东 ...
- spring cron 定时任务
文章首发于个人博客:https://yeyouluo.github.io 0 预备知识:cron表达式 见 <5 参考>一节. 1 环境 eclipse mars2 + Maven3.3. ...
- Spring task定时任务执行一段时间后莫名其妙停止的问题
前因: 我写了一个小项目,主要功能是用Spring task定时任务每天定时给用户发送邮件.执行了几个月一直没有问题,前几天,莫名其妙的突然不再发送邮件了. 只好花费一些时间来查看到底是什么原因造成的 ...
- cron定时任务介绍
什么是cron? Cron是linux系统中用来定期执行或指定程序任务的一种服务或软件.与它相关的有两个工具:crond 和 crontab.crond 就是 cron 在系统内的宿主程序,cront ...
- spring task 实现定时执行(补充:解决定时任务执行2次问题)
首先在spring-mvc.xml配置头文件引入: xmlns:task="http://www.springframework.org/schema/task" 其次引入task ...
- linux之cron定时任务介绍
前言 linux系统有一个专门用来管理定时任务的进程cron,一般是设置成开机自启动的,通过添加任务可以让服务器定时执行某些任务. cron介绍 linux系统有一个专门用来管理定时任务的进程cron ...
随机推荐
- React技巧之打开文件输入框
原文链接:https://bobbyhadz.com/blog/react-open-file-input-on-button-click 作者:Borislav Hadzhiev 正文从这开始~ 总 ...
- 数字格式化的 js 库
数字格式化的 js 库 Numeral.js,是一个用于格式化数字和处理数字的 js 库. Tip:目前 Star 有 9.2k,5年以前就没有在更新.其文档写得不很清晰,比如它提供了多语言,但如何切 ...
- 【python基础】第02回 计算机基础2
上节内容回顾 1.绝对路径与相对路径 1.路径的概念 用来标识资源的位置 2.绝对路径 类似于全球GPS定位(给到任何人都可以顺利的找到相应的资源) eg: D:\aaa\a.txt 3.相对路径 需 ...
- 在mybatis中使用sum函数返回对象为Null
首先大家看一下我的XML中的SQL .DAO 和实体对象 XML DAO PO 乍一看 没毛病. 但是在Mybatis中使用sum函数,如果返回值是0(就是你在Navicat中运行的的sql正常,结 ...
- C语言求100以内的和的4种方式
C语言的一个很经典的例子,帮助熟练运行几个循环的写法 * 方法一(do---while语句) #include main () { int i,sum=0; do { sum=sum+i; i++; ...
- Nacos 的安装与服务的注册
Nacos 的安装与服务的注册 我们都知道naocs是一个注册中心,那么注册中心是什么呢? 什么是注册中心? 它类似与一个中介角色(不收费的良心中介), 在微服务中起纽带的作用,它提供了服务和服务地址 ...
- CF989C A Mist of Florescence 题解
因为 \(1 \leq a,b,c,d \leq 100\) 所以每一个颜色都有属于自己的联通块. 考虑 \(a = b=c=d=1\) 的情况. AAAAAAAAAAAAAAAAAAAAAAAAAA ...
- TypeScript 接口继承
1.TypeScript 接口继承 和类一样,接口也可以通过关键字 extents 相互继承.接口继承,分为:单继承和多继承,即继承多个接口.另外,接口也可以继承类,它会继承类的成员,但不包括具体的实 ...
- JDBC:批处理
1.批处理: 当要执行某条SQL语句很多次时.例如,批量添加数据:使用批处理的效率要高的多. 2.如何实现批处理 实践: package com.dgd.test; import java.io.Fi ...
- SpringBoot接口 - 如何优雅的对参数进行校验?
在以SpringBoot开发Restful接口时, 对于接口的查询参数后台也是要进行校验的,同时还需要给出校验的返回信息放到上文我们统一封装的结构中.那么如何优雅的进行参数的统一校验呢? @pdai ...