<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在很多.net開發體系中開發者在面對排程作業需求的時候一般會選擇三方開源成熟的作業排程框架來滿足業務需求,比如Hangfire、Quartz.NET這樣的框架。但是有些時候可能我們只是需要一個簡易的延遲任務,這個時候引入這些框架就費力不討好了。
最簡單的粗暴的辦法當然是:
Task.Run(async () => { //延遲xx毫秒 await Task.Delay(time); //業務執行 });
當時作為一個開發者,有時候還是希望使用更優雅的、可複用的一體化方案,比如可以實現一個簡易的時間輪來完成基於記憶體的非核心重要業務的延遲排程。什麼是時間輪呢,其實就是一個環形陣列,每一個陣列有一個插槽代表對應時刻的任務,陣列的值是一個任務佇列,假設我們有一個基於60秒的延遲時間輪,也就是說我們的任務會在不超過60秒(超過的情況增加分鐘插槽,下面會講)的情況下執行,那麼如何實現?下面我們將定義一段程式碼來實現這個簡單的需求
話不多說,擼程式碼,首先我們需要定義一個時間輪的Model類用於承載我們的延遲任務和任務處理器。簡單定義如下:
public class WheelTask<T> { public T Data { get; set; } public Func<T, Task> Handle { get; set; } }
定義很簡單,就是一個入參T代表要執行的任務所需要的入參,然後就是任務的具體處理器Handle。接著我們來定義時間輪本輪的核心程式碼:
可以看到時間輪其實核心就兩個東西,一個是毫秒計時器,一個是陣列插槽,這裡陣列插槽我們使用了字典來實現,key值分別對應0到59秒。每一個插槽的value對應一個任務佇列。當新增一個新任務的時候,輸入需要延遲的秒數,就會將任務插入到延遲多少秒對應的插槽內,當計時器啟動的時候,每一跳剛好1秒,那麼就會對插槽計數+1,然後去尋找當前插槽是否有任務,有的話就會呼叫ExecuteTask執行該插槽下的所有任務。
public class TimeWheel<T> { int secondSlot = 0; DateTime wheelTime { get { return new DateTime(1, 1, 1, 0, 0, secondSlot); } } Dictionary<int, ConcurrentQueue<WheelTask<T>>> secondTaskQueue; public void Start() { new Timer(Callback, null, 0, 1000); secondTaskQueue = new Dictionary<int, ConcurrentQueue<WheelTask<T>>>(); Enumerable.Range(0, 60).ToList().ForEach(x => { secondTaskQueue.Add(x, new ConcurrentQueue<WheelTask<T>>()); }); } public async Task AddTaskAsync(int second, T data, Func<T, Task> handler) { var handTime = wheelTime.AddSeconds(second); if (handTime.Second != wheelTime.Second) secondTaskQueue[handTime.Second].Enqueue(new WheelTask<T>(data, handler)); else await handler(data); } async void Callback(object o) { if (secondSlot != 59) secondSlot++; else { secondSlot = 0; } if (secondTaskQueue[secondSlot].Any()) await ExecuteTask(); } async Task ExecuteTask() { if (secondTaskQueue[secondSlot].Any()) while (secondTaskQueue[secondSlot].Any()) if (secondTaskQueue[secondSlot].TryDequeue(out WheelTask<T> task)) await task.Handle(task.Data); } }
接下來就是如果我需要大於60秒的情況如何處理呢。其實就是增加分鐘插槽陣列,舉個例子我有一個任務需要2分40秒後執行,那麼當我 插入到時間輪的時候我先插入到分鐘插槽,當計時器每過去60秒,分鐘插槽值+1,當分鐘插槽對應有任務的時候就將這些任務從分鐘插槽裡彈出再入隊到秒插槽中,這樣一個任務會先進入插槽值=2(假設從0開始計算)的分鐘插槽,計時器執行120秒後分鍾值從0累加到2,2插槽的任務彈出到插槽值=40的秒插槽裡,當計時器再執行40秒,剛好就可以執行這個延遲2分40秒的任務。話不多說,上程式碼:
首先我們將任務WheelTask增加一個Second屬性,用於當任務從分鐘插槽彈出來時需要知道自己入隊哪個秒插槽
public class WheelTask<T> { ... public int Second { get; set; } ... }
接著我們再重新定義時間輪的邏輯增加分鐘插槽值以及插槽佇列的部分
public class TimeWheel<T> { int minuteSlot, secondSlot = 0; DateTime wheelTime { get { return new DateTime(1, 1, 1, 0, minuteSlot, secondSlot); } } Dictionary<int, ConcurrentQueue<WheelTask<T>>> minuteTaskQueue, secondTaskQueue; public void Start() { new Timer(Callback, null, 0, 1000);、 minuteTaskQueue = new Dictionary<int, ConcurrentQueue<WheelTask<T>>>(); secondTaskQueue = new Dictionary<int, ConcurrentQueue<WheelTask<T>>>(); Enumerable.Range(0, 60).ToList().ForEach(x => { minuteTaskQueue.Add(x, new ConcurrentQueue<WheelTask<T>>()); secondTaskQueue.Add(x, new ConcurrentQueue<WheelTask<T>>()); }); } ... }
同樣的在新增任務的AddTaskAsync函數中我們需要增加分鐘,程式碼改為這樣,當大於1分鐘的任務會入隊到分鐘插槽中,小於1分鐘的會按原邏輯直接入隊到秒插槽中:
public async Task AddTaskAsync(int minute, int second, T data, Func<T, Task> handler) { var handTime = wheelTime.AddMinutes(minute).AddSeconds(second); if (handTime.Minute != wheelTime.Minute) minuteTaskQueue[handTime.Minute].Enqueue(new WheelTask<T>(handTime.Second, data, handler)); else { if (handTime.Second != wheelTime.Second) secondTaskQueue[handTime.Second].Enqueue(new WheelTask<T>(data, handler)); else await handler(data); } }
最後的部分就是計時器的callback以及任務執行的部分:
async void Callback(object o) { bool minuteExecuteTask = false; if (secondSlot != 59) secondSlot++; else { secondSlot = 0; minuteExecuteTask = true; if (minuteSlot != 59) minuteSlot++; else { minuteSlot = 0; } } if (minuteExecuteTask || secondTaskQueue[secondSlot].Any()) await ExecuteTask(minuteExecuteTask); } async Task ExecuteTask(bool minuteExecuteTask) { if (minuteExecuteTask) while (minuteTaskQueue[minuteSlot].Any()) if (minuteTaskQueue[minuteSlot].TryDequeue(out WheelTask<T> task)) secondTaskQueue[task.Second].Enqueue(task); if (secondTaskQueue[secondSlot].Any()) while (secondTaskQueue[secondSlot].Any()) if (secondTaskQueue[secondSlot].TryDequeue(out WheelTask<T> task)) await task.Handle(task.Data); }
基本上基於分鐘+秒的時間輪延遲任務核心功能就這些了,聰明的你一定知道如何擴充套件增加小時,天,月份甚至年份的時間輪了。雖然從程式碼邏輯上可以實現,但是大部分情況下我們使用時間輪僅僅是完成一些記憶體易失性的非核心的任務延遲排程,實現天,周,月年意義不是很大。所以基本上到小時就差不多了。再多就上作業系統來排程吧。
到此這篇關於C#基於時間輪排程實現延遲任務詳解的文章就介紹到這了,更多相關C#延遲任務內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45