TimeWheelDemo
一個基于時間輪原理的定時任務
對時間輪的理解
其實我是有一篇文章(.Net 之時間輪算法(終極版)[1])針對時間輪的理論理解的,但是,我想,為啥我看完時間輪原理后,會采用這樣的方式去實現。
可能只是一些小技巧不上大雅之堂吧,大佬看看就行了。
當然如果大佬有別的看法,也請不吝賜教,互相交流,一起進步。
項目是基于時間輪理解上的一個任務調度輕型框架
作用么,造個小輪子,順便,對任務調度的實現多一些深度的思考和了解。
這個框架實現了啥子
實現了對方法的定時 循環執行。大概樣子是下面這樣的
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
引用鏈接
[1]
?.Net 之時間輪算法(終極版):?#https://blog.csdn.net/i2blue/article/details/123608471