.Net之时间轮算法(终极版)定时任务
TimeWheelDemo
一个基于时间轮原理的定时器
对时间轮的理解
其实我是有一篇文章(.Net 之时间轮算法(终极版))针对时间轮的理论理解的,但是,我想,为啥我看完时间轮原理后,会采用这样的方式去实现。
可能只是一些小技巧不上大雅之堂吧,大佬看看就行了。
当然如果大佬有别的看法,也请不吝赐教,互相交流,一起进步。
项目是基于时间轮理解上的一个任务调度轻型框架
作用么,造个小轮子,顺便,对任务调度的实现多一些深度的思考和了解。
这个框架实现了啥子
实现了对方法的定时 循环执行。
大概样子是下面这样的
TimeWheel timeWheel = new TimeWheel();
timeWheel.AddTask(new Job("定时1", () => { Console.WriteLine($"定时每1秒 {DateTime.Now}"); }, new TimeTask(TimeSpan.FromSeconds(1))));
timeWheel.AddTask(new Job("定时2", () => { Console.WriteLine($"定时2每10执行 {DateTime.Now}"); }, new TimeTask(TimeSpan.FromSeconds(10))));
timeWheel.AddTask(new Job("CRON", () => { Console.WriteLine($"CRON 每5秒 {DateTime.Now}"); }, new CronTask("*/5 * * * * *")));
timeWheel.AddTask(new Job("死信", () => { Console.WriteLine($"死信执行 {DateTime.Now}"); }, new DelayTask(TimeSpan.FromSeconds(20))));
timeWheel.AddTask(new Job("死信1", () => { Console.WriteLine($"死信1执行 {DateTime.Now}"); }, new DelayTask(TimeSpan.FromSeconds(10))));
timeWheel.Run();
能实现,定时任务,死信任务,能支持CRON表达式
定时任务如下 (TimeTask)
timeWheel.AddTask(new Job("定时1", () => { Console.WriteLine($"定时每1秒 {DateTime.Now}"); }, new TimeTask(TimeSpan.FromSeconds(1))));
timeWheel.AddTask(new Job("定时2", () => { Console.WriteLine($"定时2每10执行 {DateTime.Now}"); }, new TimeTask(TimeSpan.FromSeconds(10))));
通过 TimeTask进行实现的
CRON定时任务 (CronTask)
主要是基于 NCrontab 库,实现对CRON表达式的解析。省的自己从头解析了
timeWheel.AddTask(new Job("CRON", () => { Console.WriteLine($"CRON 每5秒 {DateTime.Now}"); }, new CronTask("*/5 * * * * *")));
这样就能实现对特定任务的执行
死信任务,延迟任务 (DelayTask)
很多死信都是基于消息队列的,但是应该也有一些实际应用中的应用场景吧。看具体了。
timeWheel.AddTask(new Job("死信", () => { Console.WriteLine($"死信执行 {DateTime.Now}"); }, new DelayTask(TimeSpan.FromSeconds(20))));
timeWheel.AddTask(new Job("死信1", () => { Console.WriteLine($"死信1执行 {DateTime.Now}"); }, new DelayTask(TimeSpan.FromSeconds(10))));
实现
能按照指定ID名,来实现对任务的移除
比如下边的,就能直接移除死信的任务。可以别的定时器执行了任务,然后,对此任务进行清除。
timeWheel.RemoveTask("死信");
基本上,只要没有被执行的任务,都会被取消执行的。
效果图

代码详解
先看看main函数的示例
static void Main(string[] args)
{
TimeWheel timeWheel = new TimeWheel();
timeWheel.AddTask(new Job("定时1", () => { Console.WriteLine($"定时每1秒 {DateTime.Now}"); }, new TimeTask(TimeSpan.FromSeconds(1))));
timeWheel.AddTask(new Job("定时2", () => { Console.WriteLine($"定时2每10执行 {DateTime.Now}"); }, new TimeTask(TimeSpan.FromSeconds(10))));
timeWheel.AddTask(new Job("CRON", () => { Console.WriteLine($"CRON 每5秒 {DateTime.Now}"); }, new CronTask("*/5 * * * * *")));
timeWheel.AddTask(new Job("死信", () => { Console.WriteLine($"死信执行 {DateTime.Now}"); }, new DelayTask(TimeSpan.FromSeconds(20))));
timeWheel.AddTask(new Job("死信1", () => { Console.WriteLine($"死信1执行 {DateTime.Now}"); }, new DelayTask(TimeSpan.FromSeconds(10))));
timeWheel.Run();
Task.Run(() =>
{
Thread.Sleep(10 * 1000);
timeWheel.RemoveTask("死信");
Console.WriteLine("移除死信");
Thread.Sleep(10 * 1000);
timeWheel.RemoveTask("CRON");
Console.WriteLine("移除任务CRON");
});
Console.WriteLine("开始运行时间轮!");
Console.ReadLine();
}
时间调度
/// <summary>
/// 时间调度方式
/// </summary>
public interface IScheduledTask
{
/// <summary>
/// 获取下一个时间
/// </summary>
/// <returns></returns>
public DateTime? GetNextTime();
}
核心的时间轮
/// <summary>
/// 时间轮算法(终极)实现
/// 大部分都是支持秒级,所以,按照秒级进行实现
/// 任务体得有它自己的任务唯一的ID
/// </summary>
public class TimeWheel
{
/// <summary>
/// 时间调度列表
/// </summary>
private ConcurrentDictionary<long, HashSet<string>> TimeTasks { get; set; } = new();
/// <summary>
/// 任务列表
/// </summary>
private ConcurrentDictionary<string, IJob> ScheduledTasks { get; set; } = new();
/// <summary>
/// 是否运行中
/// </summary>
private bool isRuning = false;
/// <summary>
/// 运行核心
/// </summary>
public void Run()
{
isRuning = true;
Task.Run(() =>
{
while (isRuning)
{
var timeStamp = GenerateTimestamp(DateTime.Now);
Task.Run(() => { Trigger(timeStamp); });
var offset = 500 - DateTime.Now.Millisecond;
SpinWait.SpinUntil(() => false, 1000 + offset);
}
});
}
public void Stop()
{
isRuning = false;
}
/// <summary>
/// 定时触发器
/// </summary>
/// <param name="timeStamp"></param>
private void Trigger(long timeStamp)
{
var oldTimeStamp = timeStamp - 1;
var list = TimeTasks.Keys.Where(t => t <= oldTimeStamp).ToList();
foreach (var item in list)
{
TimeTasks.TryRemove(item, out var _);
}
TimeTasks.TryGetValue(timeStamp, out var result);
if (result?.Any() == true)
{
var Now = DateTime.Now;
foreach (var id in result)
{
//找到指定的任务
if (ScheduledTasks.TryGetValue(id, out IJob job))
{
Task.Run(() => { job.Execute(); });
var NewTime = job.GetNextTime();
if (NewTime.HasValue && NewTime >= Now)
{
AddTask(NewTime.Value, id);
}
}
}
}
}
/// <summary>
/// 添加任务
/// </summary>
/// <param name="dateTime"></param>
/// <param name="scheduledTask"></param>
private void AddTask(DateTime dateTime, string ID)
{
var timeStamp = GenerateTimestamp(dateTime);
TimeTasks.AddOrUpdate(timeStamp, new HashSet<string>() { ID }, (k, v) =>
{
v.Add(ID);
return v;
});
}
/// <summary>
/// 增加一个任务
/// </summary>
public void AddTask(IJob job)
{
if (ScheduledTasks.ContainsKey(job.ID))
{
throw new ArgumentException($"{nameof(job)} 参数 {nameof(job.ID)}重复!");
}
else
{
ScheduledTasks.TryAdd(job.ID, job);
}
var time = DateTime.Now;
var NewTime = job.GetNextTime();
if (NewTime.HasValue && NewTime >= time)
{
Console.WriteLine($"新增任务:{job.ID}");
AddTask(NewTime.Value, job.ID);
}
}
/// <summary>
/// 移除某个任务的Task
/// </summary>
/// <param name="ID"></param>
public void RemoveTask(string ID)
{
var ids = ScheduledTasks.Values.Where(t => t.ID == ID)?.Select(t => t.ID).ToList();
if (ids?.Any() == true)
{
foreach (var id in ids)
{
if (ScheduledTasks.TryGetValue(id, out var job))
{
job.Cancel();
ScheduledTasks.TryRemove(id, out _);
}
}
}
}
/// <summary>
/// 获取时间戳
/// </summary>
private long GenerateTimestamp(DateTime dateTime)
{
return new DateTimeOffset(dateTime.ToUniversalTime()).ToUnixTimeSeconds();
}
}
任务体 (IJob)
/// <summary>
/// 任务体
/// </summary>
public interface IJob
{
/// <summary>
/// 任务ID,唯一
/// </summary>
/// <returns></returns>
public string ID { get; }
/// <summary>
/// 脚本
/// </summary>
/// <returns></returns>
public void Execute();
/// <summary>
/// 取消执行
/// </summary>
public void Cancel();
/// <summary>
/// 获取任务执行时间
/// </summary>
/// <returns></returns>
public DateTime? GetNextTime();
}
框架特点是啥
只有一个字,轻。
用的舒服点。
有问题大家一起沟通
框架地址
https://github.com/kesshei/TimeWheelDemo
.Net之时间轮算法(终极版)定时任务的更多相关文章
- .Net 之时间轮算法(终极版)
关于时间轮算法的起始 我也认真的看了时间轮算法相关,大致都是如下的一个图 个人认为的问题 大部分文章在解释这个为何用时间轮的时候都再说 假设我们现在有一个很大的数组,专门用于存放延时任务.它的精度达到 ...
- 时间轮算法(TimingWheel)是如何实现的?
前言 我在2. SOFAJRaft源码分析-JRaft的定时任务调度器是怎么做的?这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...
- 时间轮算法的定时器(Delphi)
源码下载 http://files.cnblogs.com/lwm8246/uTimeWheel.rar D7,XE2 编译测试OK //时间轮算法的定时器 //-- : QQ unit uTimeW ...
- 时间轮算法在Netty和Kafka中的应用,为什么不用Timer、延时线程池?
大家好,我是yes. 最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Ja ...
- 延时任务-基于netty时间轮算法实现
一.时间轮算法简介 为了大家能够理解下文中的代码,我们先来简单了解一下netty时间轮算法的核心原理 时间轮算法名副其实,时间轮就是一个环形的数据结构,类似于表盘,将时间轮分成多个bucket(比如: ...
- 经典多级时间轮定时器(C语言版)
经典多级时间轮定时器(C语言版) 文章目录 经典多级时间轮定时器(C语言版) 1. 序言 2. 多级时间轮实现框架 2.1 多级时间轮对象 2.2 时间轮对象 2.3 定时任务对象 2.4 双向链表 ...
- [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用
[从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...
- kafka时间轮的原理(一)
概述 早就想写关于kafka时间轮的随笔了,奈何时间不够,技术感觉理解不到位,现在把我之前学习到的进行整理一下,以便于以后并不会忘却.kafka时间轮是一个时间延时调度的工具,学习它可以掌握更加灵活先 ...
- Kafka中时间轮分析与Java实现
在Kafka中应用了大量的延迟操作但在Kafka中 并没用使用JDK自带的Timer或是DelayQueue用于延迟操作,而是使用自己开发的DelayedOperationPurgatory组件用于管 ...
随机推荐
- 新鲜出炉:appium2.0+ 单点触控和多点触控新的解决方案
在 appium2.0 之前,在移动端设备上的触屏操作,单手指触屏和多手指触屏分别是由 TouchAction 类,Multiaction 类实现的. 在 appium2.0 之后,这 2 个方法将会 ...
- 面试突击49:说一下 JUC 中的 Exchange 交换器?
Exchange(交换器)顾名思义,它是用来实现两个线程间的数据交换的,它诞生于 JDK 1.5,它有两个核心方法: exchange(V x):等待另一个线程到达此交换点,然后将对象传输给另一个线程 ...
- CSAPP 之 CacheLab 详解
前言 本篇博客将会介绍 CSAPP 之 CacheLab 的解题过程,分为 Part A 和 Part B 两个部分,其中 Part A 要求使用代码模拟一个高速缓存存储器,Part B 要求优化矩阵 ...
- 手把手教你 bash中给变量赋值时 ' 和 " 和 ` 和 $() 的使用
1.赋值指令 var='变量内容' var="变量内容" var=`command` var=$(command) var=变量内容 2.格式要求 =两边不能有空白字符 错误示例 ...
- html5手册语义化标签
html5手册语义化标签: article section aside hgroup header footer nav time mark figure figcaption contextmenu ...
- 从零开始构建并编写神经网络---Keras【学习笔记】[1/2]
Keras简介: Keras是由纯python编写的基于theano/tensorflow的深度学习框架. Keras是一个高层神经网络API,支持快速实验,能够把你的idea迅速转换为结果, ...
- GDKOI 2021 Day2 PJ 去世记
比赛时和昨天一样困,后面的大奆打代码的速度简直了 T1 用 2.4.6.8 来与 5 抵消掉末尾的 0 ,然后用周期问题的方法直接乘起来并取个位 #include<bits/stdc++.h&g ...
- 【原创】SpringBoot 2.7.0通过lettuce及commons-pool2 v2.9.0集成Redis踩坑记录
背景 公司的一个项目由于HTTPS证书到期,导致小程序.POS不能正常使用.所以百度了下,通过URL检测证书有效期的代码,并自行整合到一个服务中. 代码仓库:[基于SpringBoot + 企业微信 ...
- vue2和vue3生命周期的区别
概念 首先,我们了解一下"生命周期"这个词.通俗的来说,生命周期就是一个事务从出生到消失的过程.例如,一个人从出生到去世.在vue中,vue的生命周期是指,从创建vue对象到销毁v ...
- centos7.6部署DRBD提示“no resources defined!
环境准备: node1(主节点)IP: 192.168.26.30 主机名:node1node2(从节点)IP: 192.168.26.31 主机名:node2 1.关闭防火墙和selinux #se ...