出让执行权:Task.Yield, Dispatcher.Yield
一個(gè)耗時(shí)的任務(wù),可以通過(guò) Task.Yield 或者 Dispatcher.Yield 來(lái)中斷以便分割成多個(gè)小的任務(wù)片段執(zhí)行。
Yield?這個(gè)詞很有意思,叫做“屈服”“放棄”“讓步”,字面意義上是讓出當(dāng)前任務(wù)的執(zhí)行權(quán),轉(zhuǎn)而讓其他任務(wù)可以插入執(zhí)行。Task、Dispatcher、Thread?都有?Yield()?方法,看起來(lái)都可以讓出當(dāng)前任務(wù)的執(zhí)行權(quán)。
本文內(nèi)容
Dispatcher.Yield
需要注意
Task.Yield
如果在閱讀中發(fā)現(xiàn)對(duì)本文涉及到的一些概念不太明白,可以閱讀:
深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)
深入了解 WPF Dispatcher 的工作原理(PushFrame 部分)
如果一個(gè)方法的實(shí)現(xiàn)比較耗時(shí),為了不影響 UI 的響應(yīng),你會(huì)選擇用什么方法呢?我之前介紹過(guò)的?Invoke 和 InvokeAsync?可以解決,將后續(xù)耗時(shí)的任務(wù)分割成一個(gè)個(gè)小的片段以低于用戶輸入和渲染的優(yōu)先級(jí)執(zhí)行。
Dispatcher.Yield?也可以,其行為更加類似于?Dispatcher.InvokeAsync(即采用?Dispatcher?調(diào)度的方式,事實(shí)上后面會(huì)說(shuō)到其實(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í)例方法。不過(guò) 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。
拿前面?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í)行。
如果覺得?Task.Yield()?的用途難以理解,可以參考?dudu?的博客?終于明白了 C# 中 Task.Yield 的用途 - dudu - 博客園。
參考資料
c# - Task.Yield - real usages? - Stack Overflow
Task.Yield Method (System.Threading.Tasks)
c# - Difference between Synchronization Context and Dispatcher - Stack Overflow
原文地址:https://walterlv.com/post/yield-in-task-dispatcher.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號(hào)文章匯總?http://www.csharpkit.com?
總結(jié)
以上是生活随笔為你收集整理的出让执行权:Task.Yield, Dispatcher.Yield的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记一次ORM的权衡和取舍
- 下一篇: VS Code 即将迎来再一次的 log