不要使用 Dispatcher.Invoke,由於它可能在你的延遲初始化 Lazy 中致使死鎖

原文: 不要使用 Dispatcher.Invoke,由於它可能在你的延遲初始化 Lazy 中致使死鎖

版權聲明:本做品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、從新發布,但務必保留文章署名呂毅(包含連接:https://walterlv.blog.csdn.net/),不得用於商業目的,基於本文修改後的做品務必以相同的許可發佈。若有任何疑問,請與我聯繫(walter.lv@qq.com)。 https://blog.csdn.net/WPwalter/article/details/85222847

WPF 中爲了 UI 的跨線程訪問,提供了 Dispatcher 線程模型。其 Invoke 方法,不管在哪一個線程調用,均可以讓傳入的方法回到 UI 線程。html

然而,若是你在 Lazy 上下文中使用了 Invoke,那麼當這個 Lazy<T> 跨線程併發時,極有可能致使死鎖。本文將具體說說這個例子。安全


一段死鎖的代碼

請先看一段很是簡單的 WPF 代碼:markdown

private Lazy<Walterlv> _walterlvLazy = new Lazy<Walterlv>(() => new Walterlv());

private void OnLoaded(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        // 在後臺線程經過 Lazy 獲取。
        var backgroundWalterlv = _walterlvLazy.Value;
    });

    // 等待一個時間,這樣能夠確保後臺線程先訪問到 Lazy,而且在完成以前,UI 線程也能訪問到 Lazy。
    Thread.Sleep(50);

    // 在主線程經過 Lazy 獲取。
    var walterlv = _walterlvLazy.Value;
}

而其中的 Walterlv 類的定義也是很是簡單的:併發

class Walterlv
{
    public Walterlv()
    {
        // 等待一段時間,是爲了給我麼的測試程序一個準確的時機。
        Thread.Sleep(100);

        // Invoke 到主線程執行,裏面什麼都不作是爲了證實毫不是裏面代碼帶來的影響。
        Application.Current.Dispatcher.Invoke(() =>
        {
        });
    }
}

這裏的 Application.Current.Dispatcher 並不必定必須是 Application.Current,只要是兩個不一樣線程拿到的 Dispatcher 的實例是同一個,就會死鎖。異步

此死鎖的觸發條件

  1. Lazy<T> 的線程安全參數設置爲默認的,也就是 LazyThreadSafetyMode.ExecutionAndPublication
  2. 後臺線程和主 UI 線程併發訪問這個 Lazy<T>,且後臺線程先於主 UI 線程訪問這個 Lazy<T>
  3. Lazy<T> 內部的代碼包含主線程的 Invoke

此死鎖的緣由

  1. 後臺線程訪問到 Lazy,因而 Lazy 內部得到同步鎖;
  2. 主 UI 線程訪問到 Lazy,因而主 UI 線程等待同步鎖完成,並進入阻塞狀態(以致於不能處理消息循環);
  3. 後臺線程的初始化調用到 Invoke 須要到 UI 線程完成指定的任務後纔會返回,但 UI 線程此時阻塞不能處理消息循環,以致於沒法完成 Invoke 內的任務;

因而,後臺線程在等待 UI 線程處理消息以便讓 Invoke 完成,而主 UI 線程因爲進入 Lazy 的等待,因而不能完成 Invoke 中的任務;因而發生死鎖。async

此死鎖的解決方法

Invoke 改成 InvokeAsync 便能解鎖。post

這麼作能解決的緣由是:後臺線程可以及時返回,這樣 UI 線程便可以繼續執行,包括執行 InvokeAsync 中傳入的任務。測試

實際上,以上多是最好的解決辦法了。由於:ui

  1. 咱們使用 Lazy 而且設置線程安全,必定是由於這個初始化過程會被多個線程訪問;
  2. 咱們會在 Lazy 的初始化代碼中使用回到主線程的 Invoke,也是由於咱們預料到這份初始化代碼可能在後臺線程執行。

因此,這段初始化代碼既然不可避免地會併發,那麼就應該阻止併發形成的死鎖問題。也就是不要使用 Invoke 而是改用 InvokeAsyncatom

若是須要使用 Invoke 的返回值,那麼改成 InvokeAsync 以後,可使用 await 異步等待返回值。

更多死鎖問題

死鎖問題:

解決方法:

相關文章
相關標籤/搜索