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

前言

今天, 是系列文的小小里程碑, 我们终於在今天找出了 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 了!

明天见 !


<<:  指标

>>:  2021 — 找工作 (上)

D3JsDay10 遇到元素资料不相等,用函式解决高人一等

绑定的资料和画面上的元素不相等 enter()函式—没放入元素的资料 先看以下程序码 <bod...

Day 14:965. Univalued Binary Tree

今日题目 题目连结:965. Univalued Binary Tree 题目主题:Tree, De...

[前端暴龙机,Vue2.x 进化 Vue3 ] Day4. Vue的生命周期

开心,终於正式进入 Vue 的介绍了~ 在了解 Vue 的写法前,有个重要的观念必需去了解,那就是 ...

[Day29] - kali 0x4 Metasploit

Day29 - kali 0x4 Metasploit 前言 昨天提完破密工具之後,今天来讲讲这个e...

【从实作学习ASP.NET Core】Day18 | 後台 | 会员的 CRUD 页面

今天接续昨天的内容,把会员管理页面做一个收尾 使用者列表 这边可以用 ViewModel 来呈现使用...