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
异步等待返回值。
死锁问题:
解决方法: