我也认真的看了时间轮算法相关,大致都是如下的一个图
大部分文章在解释这个为何用时间轮的时候都再说
假设我们现在有一个很大的数组,专门用于存放延时任务。它的精度达到了毫秒级!那么我们的延迟任务实际上需要将定时的那个时间简单转换为毫秒即可,然后将定时任务存入其中:
比如说当前的时间是 2018/10/24 19:43:45,那么就将任务存入 Task[1540381425000],value 则是定时任务的内容。
假如这个数组长度达到了亿亿级,我们确实可以这么干。 那如果将精度缩减到秒级呢?我们也需要一个百亿级长度的数组。
先不说内存够不够,显然你的定时器要这么大的内存显然很浪费。
为什么要用时间轮实现
时间轮定时器最大的优点
举例说明
比如说当前的时间是 2018/10/24 19:43:45,那么就将任务存入 Task[1540381425000],value 则是定时任务的内容。
private Task[很长] tasks;
public List<Task> getTaskList(long timestamp) {
return task.get(timestamp)
}
// 假装这里真的能一毫秒一个循环
public void run(){
while (true){
getTaskList(System.currentTimeMillis()).后台执行()
Thread.sleep(1);
}
}
假如这个数组长度达到了亿亿级,我们确实可以这么干。 那如果将精度缩减到秒级呢?我们也需要一个百亿级长度的数组。
先不说内存够不够,显然你的定时器要这么大的内存显然很浪费。
对于以上的描述,我自己还是很不认同的,我为啥要声明这么大的数组,难道不能有一个任务,我就放一个任务么,实际数组的长度应该是你任务数的长度吧。
要不然,为啥要这么浪费。想不通,可能还有别的解释,谁有答案可以告诉我。
在实际的使用中,一般都为秒级,毫秒级都很少,因为毫秒级的不准。
所以,我可以根据这些通过 hash 字典构建一个 这样的时间轮算法,也作为我自己想实现定时任务框架的一部分。
逻辑:
核心为一个字典,key 为时间戳,值为任务列表。
整体就是每个添加的任务都有一个触发的时间(秒),到了这个秒,时间就会触发,自然会去执行相关的任务。
有了任务才会添加,不会任何任务都添加的。
任务触发的时候,会获取任务下一次执行的时间,并插入到任务列表里。
static void Main(string[] args)
{
ScheduledTask scheduledTask = new ScheduledTask(() => { Console.WriteLine($"{DateTime.Now}"); }, new TimeSpan(0, 0, 5));
TimeWheel timeWheel = new TimeWheel();
timeWheel.AddTask(scheduledTask);
timeWheel.Run();
Console.WriteLine("开始运行时间轮!");
Console.ReadLine();
}
/// <summary>
/// 时间轮算法(终极)实现
/// 大部分都是支持秒级,所以,按照秒级进行实现
/// </summary>
public class TimeWheel
{
/// <summary>
/// 任务列表
/// </summary>
public ConcurrentDictionary<long, List<IScheduledTask>> Tasks = new();
public void Run()
{
while (true)
{
var now = DateTime.Now;
Task.Run(() => { Trigger(now); });
var offset = 500 - now.Millisecond;
SpinWait.SpinUntil(() => false, 1000 + offset);
}
}
public void Trigger(DateTime dateTime)
{
var timeStamp = GenerateTimestamp(dateTime);
var oldTimeStamp = timeStamp - 1;
Tasks.TryRemove(oldTimeStamp, out var _);
Tasks.TryGetValue(timeStamp, out var result);
if (result?.Any() == true)
{
foreach (var item in result)
{
Task.Run(item.GetAction());
var NewTime = item.GetNextTime();
if (NewTime.HasValue)
{
AddTask(NewTime.Value, item);
}
}
}
}
public void AddTask(IScheduledTask scheduledTask)
{
var timeStamp = GenerateTimestamp(scheduledTask.GetNextTime().Value);
Tasks.AddOrUpdate(timeStamp, new List<IScheduledTask>() { scheduledTask }, (k, v) =>
{
v.Add(scheduledTask);
return v;
});
}
public void AddTask(DateTime dateTime, IScheduledTask scheduledTask)
{
var timeStamp = GenerateTimestamp(dateTime);
Tasks.AddOrUpdate(timeStamp, new List<IScheduledTask>() { scheduledTask }, (k, v) =>
{
v.Add(scheduledTask);
return v;
});
}
private long GenerateTimestamp(DateTime dateTime)
{
return new DateTimeOffset(dateTime.ToUniversalTime()).ToUnixTimeSeconds();
}
private DateTime GetDatetime(long timeStamp)
{
var d = DateTimeOffset.FromUnixTimeSeconds(timeStamp);
return d.LocalDateTime;
}
}
public interface IScheduledTask
{
public Action GetAction();
public DateTime? GetNextTime();
}
/// <summary>
/// 定时器任务,普通任务
/// 间隔指定的时间
/// </summary>
public class ScheduledTask : IScheduledTask
{
private Action _action;
private TimeSpan timeSpan;
public ScheduledTask(Action action, TimeSpan timeSpan)
{
this._action = action;
this.timeSpan = timeSpan;
}
public Action GetAction()
{
return this._action;
}
public DateTime? GetNextTime()
{
return DateTime.Now.AddSeconds(timeSpan.TotalSeconds);
}
}
最后的效果
当然,也可以通过 CRON 表达式来实现更为高级的。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章