出让执行权:Task.Yield, Dispathcer.Yield
?
Yield 這個詞很有意思,叫做“屈服”“放棄”“讓步”,字面意義上是讓出當前任務的執行權,轉而讓其他任務可以插入執行。Task、Dispatcher、Thread 都有 Yield() 方法,看起來都可以讓出當前任務的執行權。
如果在閱讀中發現對本文涉及到的一些概念不太明白,可以閱讀:
- 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)
- 深入了解 WPF Dispatcher 的工作原理(PushFrame 部分)
Dispatcher.Yield
如果一個方法的實現比較耗時,為了不影響 UI 的響應,你會選擇用什么方法呢?我之前介紹過的 Invoke 和 InvokeAsync 可以解決,將后續耗時的任務分割成一個個小的片段以低于用戶輸入和渲染的優先級執行。
Dispatcher.Yield 也可以,其行為更加類似于 Dispatcher.InvokeAsync(即采用 Dispatcher 調度的方式,事實上后面會說到其實就是調用了 InvokeAsync),而非 Dispatcher.Invoke(即采用 PushFrame 新開消息循環的方式)。
使用時需要 await:
foreach(var item in collection) {DoWorkWhichWillTakeHalfASecond();await Dispatcher.Yield(); }這樣,這個 foreach 將在每遍歷到一個集合項的時候中斷一次,讓 UI 能夠響應用戶的交互輸入和渲染。
Yield 方法可以傳入一個優先級參數,指示繼續執行后續任務的優先級。默認是 DispatcherPriority.Background,低于用戶輸入 DispatcherPriority.Input、 UI 邏輯 DispatcherPriority.Loaded 和渲染 DispatcherPriority.Render。
Dispatcher.Yield 是如何做到出讓執行權的呢?
查看源碼,發現 DispatcherYield 的返回值是 DispatcherPriorityAwaiter,而它的 OnCompleted 方法是這樣的:
public void OnCompleted(Action continuation) {if(_dispatcher == null)throw new InvalidOperationException(SR.Get(SRID.DispatcherPriorityAwaiterInvalid));_dispatcher.InvokeAsync(continuation, _priority); }所以,其實真的就是 InvokeAsync。如果希望了解為何是 OnCompleted 方法,可以閱讀 【C#】【多線程】【05-使用C#6.0】08-自定義awaitable類型 - L.M。
需要注意
Dispatcher.Yield 是 Dispatcher 類型的靜態方法,而不是像 InvokeAsync 一樣是實例方法。不過 C# 有一個神奇的特性——靜態方法和實例方法可以在同一上下文中調用,而不用擔心產生歧義。
例如:
using System.Windows.Threading;class Demo : DispatcherObject {void Test(){// 調用靜態方法 Yield。await Dispatcher.Yield();// 調用實例方法 InvokeAsync。await Dispatcher.InvokeAsync(() => { });} }注意需要引用命名空間 System.Windows.Threading。
Task.Yield
拿前面 Dispatcher.Yield 的例子,我們換成 Task.Yield:
foreach(var item in collection) {DoWorkWhichWillTakeHalfASecond();await Task.Yield(); }效果與 Dispatcher.Yield(DispatcherPriority.Normal) 是一樣的。因為 Task 調度回到線程上下文靠的是 SynchronizationContext,WPF UI 線程的 SynchronizationContext 被設置為了 DispatcherSynchronizationContext,使用 Dispatcher 調度;而 DispatcherSynchronizationContext 構造時傳入的優先級默認是 Normal,WPF 并沒有特殊傳入一個別的值,所以 WPF UI 線程上使用 Task.Yield() 出讓執行權后,恢復時使用的是 Normal 優先級,相當于 Dispatcher.Yield(DispatcherPriority.Normal)。
希望了解 Dispatcher 和 SynchronizationContext 的區別可以閱讀 c# - Difference between Synchronization Context and Dispatcher - Stack Overflow。
DispatcherSynchronizationContext 執行 await 后續任務的上下文代碼:
/// <summary> /// Asynchronously invoke the callback in the SynchronizationContext. /// </summary> public override void Post(SendOrPostCallback d, Object state) {// Call BeginInvoke with the cached priority. Note that BeginInvoke// preserves the behavior of passing exceptions to// Dispatcher.UnhandledException unlike InvokeAsync. This is// desireable because there is no way to await the call to Post, so// exceptions are hard to observe._dispatcher.BeginInvoke(_priority, d, state); }既然是 Normal 優先級,那么在 UI 線程上的效果自然不如 Dispatcher.Yield。但是,Task.Yield 適用于任何線程,因為 SynchronizationContext 本身是與 Dispatcher 無關的,適用于任何線程。這樣,于如果一個 Task 內部的任務太耗時,用 Task.Yield 則可以做到將此任務分成很多個片段執行。
參考資料
- https://stackoverflow.com/questions/23431595/task-yield-real-usages“>c# - Task.Yield - real usages? - Stack Overflow
- Task.Yield Method (System.Threading.Tasks)
- c# - Difference between Synchronization Context and Dispatcher - Stack Overflow
轉載于:https://www.cnblogs.com/walterlv/p/10236535.html
總結
以上是生活随笔為你收集整理的出让执行权:Task.Yield, Dispathcer.Yield的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GIT 团队协作快速入门使用
- 下一篇: easyUI 学习网站