<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C# 基于時(shí)間輪調(diào)度的延遲任務(wù)實(shí)現(xiàn)

          共 8085字,需瀏覽 17分鐘

           ·

          2023-11-11 23:40

          在很多.NET 開發(fā)體系中開發(fā)者在面對調(diào)度作業(yè)需求的時(shí)候一般會(huì)選擇三方開源成熟的作業(yè)調(diào)度框架來滿足業(yè)務(wù)需求,比如Hangfire、Quartz.NET這樣的框架。

          但是有些時(shí)候可能我們只是需要一個(gè)簡易的延遲任務(wù),這個(gè)時(shí)候引入這些框架就費(fèi)力不討好了。

          最簡單的粗暴的辦法當(dāng)然是:

          Task.Run(async () =>
          {
            //延遲xx毫秒
            await Task.Delay(time);
            //業(yè)務(wù)執(zhí)行
          });

          當(dāng)時(shí)作為一個(gè)開發(fā)者,有時(shí)候還是希望使用更優(yōu)雅的、可復(fù)用的一體化方案,比如可以實(shí)現(xiàn)一個(gè)簡易的時(shí)間輪來完成基于內(nèi)存的非核心重要業(yè)務(wù)的延遲調(diào)度。什么是時(shí)間輪呢,其實(shí)就是一個(gè)環(huán)形數(shù)組,每一個(gè)數(shù)組有一個(gè)插槽代表對應(yīng)時(shí)刻的任務(wù),數(shù)組的值是一個(gè)任務(wù)隊(duì)列,假設(shè)我們有一個(gè)基于60秒的延遲時(shí)間輪,也就是說我們的任務(wù)會(huì)在不超過60秒(超過的情況增加分鐘插槽,下面會(huì)講)的情況下執(zhí)行,那么如何實(shí)現(xiàn)?

          正文

          下面我們將定義一段代碼來實(shí)現(xiàn)這個(gè)簡單的需求。

          話不多說,擼代碼,首先我們需要定義一個(gè)時(shí)間輪的Model類用于承載我們的延遲任務(wù)和任務(wù)處理器。簡單定義如下:

          public class WheelTask<T>
          {
            public T Data { getset; }
            public Func<T, Task> Handle { getset; }
          }

          定義很簡單,就是一個(gè)入?yún)代表要執(zhí)行的任務(wù)所需要的入?yún)ⅲ缓缶褪侨蝿?wù)的具體處理器Handle。

          接著我們來定義時(shí)間輪本輪的核心代碼:

          可以看到時(shí)間輪其實(shí)核心就兩個(gè)東西,一個(gè)是毫秒計(jì)時(shí)器,一個(gè)是數(shù)組插槽,這里數(shù)組插槽我們使用了字典來實(shí)現(xiàn),key值分別對應(yīng)0到59秒。每一個(gè)插槽的value對應(yīng)一個(gè)任務(wù)隊(duì)列。

          當(dāng)添加一個(gè)新任務(wù)的時(shí)候,輸入需要延遲的秒數(shù),就會(huì)將任務(wù)插入到延遲多少秒對應(yīng)的插槽內(nèi),當(dāng)計(jì)時(shí)器啟動(dòng)的時(shí)候,每一跳剛好1秒,那么就會(huì)對插槽計(jì)數(shù)+1,然后去尋找當(dāng)前插槽是否有任務(wù),有的話就會(huì)調(diào)用ExecuteTask執(zhí)行該插槽下的所有任務(wù)。

          public class TimeWheel<T>
          {
            int secondSlot = 0;
            DateTime wheelTime { get { return new DateTime(11100, secondSlot); } }
            Dictionary<int, ConcurrentQueue<WheelTask<T>>> secondTaskQueue;
            public void Start()
            {
              new Timer(Callback, null01000);
              secondTaskQueue = new Dictionary<int, ConcurrentQueue<WheelTask<T>>>();
              Enumerable.Range(060).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秒的情況如何處理呢。其實(shí)就是增加分鐘插槽數(shù)組,舉個(gè)例子我有一個(gè)任務(wù)需要2分40秒后執(zhí)行,那么當(dāng)我插入到時(shí)間輪的時(shí)候我先插入到分鐘插槽,當(dāng)計(jì)時(shí)器每過去60秒,分鐘插槽值+1,當(dāng)分鐘插槽對應(yīng)有任務(wù)的時(shí)候就將這些任務(wù)從分鐘插槽里彈出再入隊(duì)到秒插槽中,這樣一個(gè)任務(wù)會(huì)先進(jìn)入插槽值=2(假設(shè)從0開始計(jì)算)的分鐘插槽,計(jì)時(shí)器運(yùn)行120秒后分鐘值從0累加到2,2插槽的任務(wù)彈出到插槽值=40的秒插槽里,當(dāng)計(jì)時(shí)器再運(yùn)行40秒,剛好就可以執(zhí)行這個(gè)延遲2分40秒的任務(wù)。

          話不多說,上代碼:

          首先我們將任務(wù)WheelTask增加一個(gè)Second屬性,用于當(dāng)任務(wù)從分鐘插槽彈出來時(shí)需要知道自己入隊(duì)哪個(gè)秒插槽

          public class WheelTask<T>
          {
            ...
            public int Second { getset;}
            ...
          }

          接著我們再重新定義時(shí)間輪的邏輯增加分鐘插槽值以及插槽隊(duì)列的部分

          public class TimeWheel<T>
          {
            int minuteSlot, secondSlot = 0;
            DateTime wheelTime { get { return new DateTime(1110, minuteSlot, secondSlot); } }
            Dictionary<int, ConcurrentQueue<WheelTask<T>>> minuteTaskQueue, secondTaskQueue;
            public void Start()
            {
              new Timer(Callback, null01000);、
              minuteTaskQueue = new Dictionary<int, ConcurrentQueue<WheelTask<T>>>();
              secondTaskQueue = new Dictionary<int, ConcurrentQueue<WheelTask<T>>>();
              Enumerable.Range(060).ToList().ForEach(x =>
              {
                minuteTaskQueue.Add(x, new ConcurrentQueue<WheelTask<T>>());
                secondTaskQueue.Add(x, new ConcurrentQueue<WheelTask<T>>());
              });
            }
            ...
          }

          同樣的在添加任務(wù)的AddTaskAsync函數(shù)中我們需要增加分鐘,代碼改為這樣,當(dāng)大于1分鐘的任務(wù)會(huì)入隊(duì)到分鐘插槽中,小于1分鐘的會(huì)按原邏輯直接入隊(duì)到秒插槽中:

          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);
              }
          }

          最后的部分就是計(jì)時(shí)器的callback以及任務(wù)執(zhí)行的部分:

          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);
          }

          總結(jié)

          基本上基于分鐘+秒的時(shí)間輪延遲任務(wù)核心功能就這些了,聰明的你一定知道如何擴(kuò)展增加小時(shí),天,月份甚至年份的時(shí)間輪了。

          雖然從代碼邏輯上可以實(shí)現(xiàn),但是大部分情況下我們使用時(shí)間輪僅僅是完成一些內(nèi)存易失性的非核心的任務(wù)延遲調(diào)度,實(shí)現(xiàn)天,周,月年意義不是很大。所以基本上到小時(shí)就差不多了。再多就上作業(yè)系統(tǒng)來調(diào)度吧。

          轉(zhuǎn)自:a1010

          鏈接:cnblogs.com/gmmy/p/17015538.html







          回復(fù) 【關(guān)閉】學(xué)永久關(guān)閉App開屏廣告
          回復(fù) 【刪除】學(xué)自動(dòng)檢測那個(gè)微信好友刪除、拉黑
          回復(fù) 【手冊】獲取3萬字.NET、C#工程師面試手冊
          回復(fù) 【幫助】獲取100+個(gè)常用的C#幫助類庫
          回復(fù) 【加群】加入DotNet學(xué)習(xí)交流群

          瀏覽 746
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲AV无码成人精品区大猫 | 成人美女视频在线观看18 | 五月天色色网址 | 亚洲综合在线网 | 亚洲日韩中文在线观看 |