出让执行权:Task.Yield, Dispatcher.Yield
一個耗時的任務,可以通過 Task.Yield 或者 Dispatcher.Yield 來中斷以便分割成多個小的任務片段執行。
Yield?這個詞很有意思,叫做“屈服”“放棄”“讓步”,字面意義上是讓出當前任務的執行權,轉而讓其他任務可以插入執行。Task、Dispatcher、Thread?都有?Yield()?方法,看起來都可以讓出當前任務的執行權。
本文內容
Dispatcher.Yield
需要注意
Task.Yield
如果在閱讀中發現對本文涉及到的一些概念不太明白,可以閱讀:
深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)
深入了解 WPF Dispatcher 的工作原理(PushFrame 部分)
如果一個方法的實現比較耗時,為了不影響 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。
拿前面?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?則可以做到將此任務分成很多個片段執行。
如果覺得?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社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結
以上是生活随笔為你收集整理的出让执行权:Task.Yield, Dispatcher.Yield的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记一次ORM的权衡和取舍
- 下一篇: VS Code 即将迎来再一次的 log