iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
自我挑戰組

從C到JS的同步非同步探索系列 第 10

[Day 10] .Net Task 底層(3)

  • 分享至 

  • xImage
  •  

前言

今天, 是系列文的小小里程碑, 我們終於在今天找出了 task 的其中一種非同步演算法。
他被用在讓 Task 接著執行下一個任務的 method 中。

讓 Task 接著執行下一個任務

查看昨天提到執行這件事的 method ITaskCompletionAction.Invoke(this)

// Interface to which all completion actions must conform.
// This interface allows us to combine functionality and reduce allocations.
// For example, see Task.SetOnInvokeMres, and its use in Task.SpinThenBlockingWait().
// This code:
//      ManualResetEvent mres = new ManualResetEventSlim(false, 0);
//      Action<Task> completionAction = delegate { mres.Set() ; };
//      AddCompletionAction(completionAction);
// gets replaced with this:
//      SetOnInvokeMres mres = new SetOnInvokeMres();
//      AddCompletionAction(mres);
// For additional examples of where this is used, see internal classes Task.SignalOnInvokeCDE,
// Task.WhenAllPromise, Task.WhenAllPromise<T>, TaskFactory.CompleteOnCountdownPromise,
// TaskFactory.CompleteOnCountdownPromise<T>, and TaskFactory.CompleteOnInvokePromise.
internal interface ITaskCompletionAction
{
    void Invoke(Task completingTask);
}

發現其原來僅僅只是一個介面, 本身的功能是由他的繼承者實踐的,

此時非常巧合的(一點都不巧 XD )我想起了在 Day7 時提到的 Task.WhenAll 流程

當 task 未完成, 就把 WhenAllPromise 的 reference 掛載到 未完成的 task 的連續任務區(這個名詞是我自己取的 XD )

而在 WhenAllPromise 中恰好有一個 method 叫 Invoke 其功能是 atomic 的減少 WhenAllPromise 中未完成的任務數量, 且當數量歸零, 結束WhenAllPromise 和回傳結果。

然後又發現了, 原來 WhenAllPromise 正式繼承自ITaskCompletionAction

自此我們拚完了 WhenAllPromise 的最後一塊拼圖, 也解釋了讓 Task 接著執行下一個任務的方法。

接著會抽象的解釋其流程, 再利用 WhenAllPromise 做舉例。

抽象流程:

  1. 資料結構

    當希望某個資料結構所要執行的事務, 可以被放到其他某個任務完成後執行時, 就將那個事務打包成名為 Invoke 的方法, 放在資料結構裡。

  2. 使用端 (假設 Thread 具有足夠承載力, 不需要把後續任務放入 TP , 可在本身 thread 繼續執行)

    當調用某"資料結構"執行某事務, 發現該事務目前無法完成, 需要留到某其他任務完成後執行, 就將該資料結構推入 Task 中的連續任務區, 在 Task 將其本身的任務完成後, 會依序觸發 Task 的連續任務區裡的資料結構的 Invoke 方法。

值得注意的是每種 CASE 有其對應的的細節, 不過概略上的邏輯差不多, 所以我僅以其中一種為例。

實際流程舉例 :

Task.WhenAll 完整流程:

Task.WhenAll( taskList ) 調用後執行動作

taskList = 待完成任務列表, 我自己命名的變數

  1. 創建 WhenAllPromise 物件, 且傳入 taskList
  2. 把 taskList 的長度(視為未完成任務數量)存在 WhenAllPromise 物件中
  3. WhenAllPromise 內部執行依序掃描 taskList 內的 task
  4. 當 task 已完成, 就把 未完成任務數量 減 1 ( 要 atomic , 因為 WhenAllPromise 有可能被掛載到別的 task 執行, 就會被視為 multi-thread )
  5. 當 task 未完成, 就把 WhenAllPromise 的 reference 掛載到 未完成的 task 的連續任務區(這個名詞是我自己取的 XD )
  6. 當一次全部掃描結束, 若是 WhenAllPromise 內的未完成任務數量為0, 表示任務全部完成, 當即設置回傳變數, 與進行回傳。
  7. 有可能未完成任務數量沒有歸零, 表示 WhenAllPromise 有在上面的第 5 步掛載到別的 task。WhenAllPromise 不會結束。
  8. 此時執行 Task.WhenAll( taskList ) 留在原地閒置。
  9. 當被 WhenAllPromise 掛載到的任務完成, 表示當初傳入的 taskList 的未完成任務數量又減了一所以在階段結束時執行被掛載的 WhenAllPromise 中的Invoke 方法。
  10. Invoke 方法會 atomic 的把未完成任務數量減 1 , 且若減完 1 未完成任務數量為 0 時, WhenAllPromise 當即設置回傳變數, 與進行回傳。
  11. 整個 Task.WhenAll( taskList ) 順利回傳 taskList 執行結果, 主程式繼續往下運行。

小結

這真的是一種有很有趣的演算法, 當你以非同步的方式要進行某件任務, 卻發現這個任務被別的任務阻塞無法完成, 就直接把自己掛載到阻塞你的對象身上, 並且要求他在自己完成後, 接著執行掛載到自己身上的任務。

以上做法其實就是一種初步的 schedule 演算法, 就如同我在第二天的 sample , 一個 httpServer ,

可以發現我雖然創建了無數個 thread 來處理 request 但是大多數的 thread 其實只是在無限迴圈中空轉等待 request 的到來, 這其實非常耗費資源, 我當時提到, 應該要有種 schedule 的方法把沒在用的 thread 送入 閒置狀態, 而我們今天讀到的就是一種方法。

若是不使用這種演算法, 運行 Task.WhenAll( taskList ) 的 thread 也需要進入一個無限迴圈, 需要不斷的檢測他追蹤的任務是否已經完成, 而如今, 直接將 thread 閒置, 讓每個任務完成時主動進行通知, 大大提高了效能。

明天進度

明天, 輪到讓 Task 進入 TP 中的 method ThreadPool.UnsafeQueueCustomWorkItem

終於可以開始聊 TP 了!

明天見 !


上一篇
[Day 9] .Net Task 底層(2)
下一篇
[Day 11] .Net Task 底層(4)
系列文
從C到JS的同步非同步探索30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言