出让执行权:Task.Yield, Dispathcer.Yield
?
Yield 這個(gè)詞很有意思,叫做“屈服”“放棄”“讓步”,字面意義上是讓出當(dāng)前任務(wù)的執(zhí)行權(quán),轉(zhuǎn)而讓其他任務(wù)可以插入執(zhí)行。Task、Dispatcher、Thread 都有 Yield() 方法,看起來(lái)都可以讓出當(dāng)前任務(wù)的執(zhí)行權(quán)。
如果在閱讀中發(fā)現(xiàn)對(duì)本文涉及到的一些概念不太明白,可以閱讀:
- 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)
- 深入了解 WPF Dispatcher 的工作原理(PushFrame 部分)
Dispatcher.Yield
如果一個(gè)方法的實(shí)現(xiàn)比較耗時(shí),為了不影響 UI 的響應(yīng),你會(huì)選擇用什么方法呢?我之前介紹過的 Invoke 和 InvokeAsync 可以解決,將后續(xù)耗時(shí)的任務(wù)分割成一個(gè)個(gè)小的片段以低于用戶輸入和渲染的優(yōu)先級(jí)執(zhí)行。
Dispatcher.Yield 也可以,其行為更加類似于 Dispatcher.InvokeAsync(即采用 Dispatcher 調(diào)度的方式,事實(shí)上后面會(huì)說到其實(shí)就是調(diào)用了 InvokeAsync),而非 Dispatcher.Invoke(即采用 PushFrame 新開消息循環(huán)的方式)。
使用時(shí)需要 await:
foreach(var item in collection) {DoWorkWhichWillTakeHalfASecond();await Dispatcher.Yield(); }這樣,這個(gè) foreach 將在每遍歷到一個(gè)集合項(xiàng)的時(shí)候中斷一次,讓 UI 能夠響應(yīng)用戶的交互輸入和渲染。
Yield 方法可以傳入一個(gè)優(yōu)先級(jí)參數(shù),指示繼續(xù)執(zhí)行后續(xù)任務(wù)的優(yōu)先級(jí)。默認(rèn)是 DispatcherPriority.Background,低于用戶輸入 DispatcherPriority.Input、 UI 邏輯 DispatcherPriority.Loaded 和渲染 DispatcherPriority.Render。
Dispatcher.Yield 是如何做到出讓執(zhí)行權(quán)的呢?
查看源碼,發(fā)現(xiàn) DispatcherYield 的返回值是 DispatcherPriorityAwaiter,而它的 OnCompleted 方法是這樣的:
public void OnCompleted(Action continuation) {if(_dispatcher == null)throw new InvalidOperationException(SR.Get(SRID.DispatcherPriorityAwaiterInvalid));_dispatcher.InvokeAsync(continuation, _priority); }所以,其實(shí)真的就是 InvokeAsync。如果希望了解為何是 OnCompleted 方法,可以閱讀 【C#】【多線程】【05-使用C#6.0】08-自定義awaitable類型 - L.M。
需要注意
Dispatcher.Yield 是 Dispatcher 類型的靜態(tài)方法,而不是像 InvokeAsync 一樣是實(shí)例方法。不過 C# 有一個(gè)神奇的特性——靜態(tài)方法和實(shí)例方法可以在同一上下文中調(diào)用,而不用擔(dān)心產(chǎn)生歧義。
例如:
using System.Windows.Threading;class Demo : DispatcherObject {void Test(){// 調(diào)用靜態(tài)方法 Yield。await Dispatcher.Yield();// 調(diào)用實(shí)例方法 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) 是一樣的。因?yàn)?Task 調(diào)度回到線程上下文靠的是 SynchronizationContext,WPF UI 線程的 SynchronizationContext 被設(shè)置為了 DispatcherSynchronizationContext,使用 Dispatcher 調(diào)度;而 DispatcherSynchronizationContext 構(gòu)造時(shí)傳入的優(yōu)先級(jí)默認(rèn)是 Normal,WPF 并沒有特殊傳入一個(gè)別的值,所以 WPF UI 線程上使用 Task.Yield() 出讓執(zhí)行權(quán)后,恢復(fù)時(shí)使用的是 Normal 優(yōu)先級(jí),相當(dāng)于 Dispatcher.Yield(DispatcherPriority.Normal)。
希望了解 Dispatcher 和 SynchronizationContext 的區(qū)別可以閱讀 c# - Difference between Synchronization Context and Dispatcher - Stack Overflow。
DispatcherSynchronizationContext 執(zhí)行 await 后續(xù)任務(wù)的上下文代碼:
/// <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 優(yōu)先級(jí),那么在 UI 線程上的效果自然不如 Dispatcher.Yield。但是,Task.Yield 適用于任何線程,因?yàn)?SynchronizationContext 本身是與 Dispatcher 無(wú)關(guān)的,適用于任何線程。這樣,于如果一個(gè) Task 內(nèi)部的任務(wù)太耗時(shí),用 Task.Yield 則可以做到將此任務(wù)分成很多個(gè)片段執(zhí)行。
參考資料
- 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
轉(zhuǎn)載于:https://www.cnblogs.com/walterlv/p/10236535.html
總結(jié)
以上是生活随笔為你收集整理的出让执行权:Task.Yield, Dispathcer.Yield的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GIT 团队协作快速入门使用
- 下一篇: easyUI 学习网站