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
的實例是同一個,就會死鎖。異步
Lazy<T>
的線程安全參數設置爲默認的,也就是 LazyThreadSafetyMode.ExecutionAndPublication
;Lazy<T>
,且後臺線程先於主 UI 線程訪問這個 Lazy<T>
;Lazy<T>
內部的代碼包含主線程的 Invoke
。Invoke
須要到 UI 線程完成指定的任務後纔會返回,但 UI 線程此時阻塞不能處理消息循環,以致於沒法完成 Invoke
內的任務;因而,後臺線程在等待 UI 線程處理消息以便讓 Invoke
完成,而主 UI 線程因爲進入 Lazy 的等待,因而不能完成 Invoke
中的任務;因而發生死鎖。async
Invoke
改成 InvokeAsync
便能解鎖。post
這麼作能解決的緣由是:後臺線程可以及時返回,這樣 UI 線程便可以繼續執行,包括執行 InvokeAsync
中傳入的任務。測試
實際上,以上多是最好的解決辦法了。由於:ui
Invoke
,也是由於咱們預料到這份初始化代碼可能在後臺線程執行。因此,這段初始化代碼既然不可避免地會併發,那麼就應該阻止併發形成的死鎖問題。也就是不要使用 Invoke
而是改用 InvokeAsync
。atom
若是須要使用 Invoke
的返回值,那麼改成 InvokeAsync
以後,可使用 await
異步等待返回值。
死鎖問題:
解決方法: