出讓執行權:Task.Yield, Dispathcer.Yield

 

Yield 這個詞頗有意思,叫作「屈服」「放棄」「讓步」,字面意義上是讓出當前任務的執行權,轉而讓其餘任務能夠插入執行。TaskDispatcherThread 都有 Yield() 方法,看起來均可以讓出當前任務的執行權。c#


若是在閱讀中發現對本文涉及到的一些概念不太明白,能夠閱讀:多線程

Dispatcher.Yield

若是一個方法的實現比較耗時,爲了避免影響 UI 的響應,你會選擇用什麼方法呢?我以前介紹過的 Invoke 和 InvokeAsync 能夠解決,將後續耗時的任務分割成一個個小的片斷以低於用戶輸入和渲染的優先級執行。ide

Dispatcher.Yield 也能夠,其行爲更加相似於 Dispatcher.InvokeAsync(即採用 Dispatcher 調度的方式,事實上後面會說到其實就是調用了 InvokeAsync),而非 Dispatcher.Invoke(即採用 PushFrame 新開消息循環的方式)。spa

使用時須要 await.net

foreach(var item in collection)
{
    DoWorkWhichWillTakeHalfASecond();
    await Dispatcher.Yield();
}

這樣,這個 foreach 將在每遍歷到一個集合項的時候中斷一次,讓 UI 可以響應用戶的交互輸入和渲染。pwa

Yield 方法能夠傳入一個優先級參數,指示繼續執行後續任務的優先級。默認是 DispatcherPriority.Background,低於用戶輸入 DispatcherPriority.Input、 UI 邏輯 DispatcherPriority.Loaded 和渲染 DispatcherPriority.Render線程

Dispatcher.Yield 是如何作到出讓執行權的呢?code

查看源碼,發現 DispatcherYield 的返回值是 DispatcherPriorityAwaiter,而它的 OnCompleted 方法是這樣的:orm

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.Mxml

須要注意

Dispatcher.YieldDispatcher 類型的靜態方法,而不是像 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)。

但願瞭解 DispatcherSynchronizationContext 的區別能夠閱讀 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 則能夠作到將此任務分紅不少個片斷執行。


參考資料

相關文章
相關標籤/搜索