WPF 中为了 UI 的跨线程访问,提供了 Dispatcher 线程模型。其 Invoke 方法,无论在哪个线程调用,都可以让传入的方法回到 UI 线程。

然而,如果你在 Lazy 上下文中使用了 Invoke,那么当这个 Lazy<T> 跨线程并发时,极有可能导致死锁。本文将具体说说这个例子。


一段死锁的代码

请先看一段非常简单的 WPF 代码:

  1. private Lazy<Walterlv> _walterlvLazy = new Lazy<Walterlv>(() => new Walterlv());
  2. private void OnLoaded(object sender, RoutedEventArgs e)
  3. {
  4. Task.Run(() =>
  5. {
  6. // 在后台线程通过 Lazy 获取。
  7. var backgroundWalterlv = _walterlvLazy.Value;
  8. });
  9. // 等待一个时间,这样可以确保后台线程先访问到 Lazy,并且在完成之前,UI 线程也能访问到 Lazy。
  10. Thread.Sleep(50);
  11. // 在主线程通过 Lazy 获取。
  12. var walterlv = _walterlvLazy.Value;
  13. }

而其中的 Walterlv 类的定义也是非常简单的:

  1. class Walterlv
  2. {
  3. public Walterlv()
  4. {
  5. // 等待一段时间,是为了给我么的测试程序一个准确的时机。
  6. Thread.Sleep(100);
  7. // Invoke 到主线程执行,里面什么都不做是为了证明绝不是里面代码带来的影响。
  8. Application.Current.Dispatcher.Invoke(() =>
  9. {
  10. });
  11. }
  12. }

这里的 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 中的任务;于是发生死锁。

此死锁的解决方法

Invoke 改为 InvokeAsync 便能解锁。

这么做能解决的原因是:后台线程能够及时返回,这样 UI 线程便能够继续执行,包括执行 InvokeAsync 中传入的任务。

实际上,以上可能是最好的解决办法了。因为:

  1. 我们使用 Lazy 并且设置线程安全,一定是因为这个初始化过程会被多个线程访问;
  2. 我们会在 Lazy 的初始化代码中使用回到主线程的 Invoke,也是因为我们预料到这份初始化代码可能在后台线程执行。

所以,这段初始化代码既然不可避免地会并发,那么就应该阻止并发造成的死锁问题。也就是不要使用 Invoke 而是改用 InvokeAsync

如果需要使用 Invoke 的返回值,那么改为 InvokeAsync 之后,可以使用 await 异步等待返回值。

更多死锁问题

死锁问题:

解决方法:

不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 Lazy 中导致死锁的更多相关文章

  1. Dispatcher.Invoke方法

    前一篇小猪分享过在WPF中简单的使用BackgroundWorker完成多线程操作!在那篇中小猪利用了BackgroundWorker组件对耗时比较多的操作放在了单独的BackgroundWorker ...

  2. 在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁

    AutoResetEvent.ManualResetEvent.Monitor.lock 等等这些用来做同步的类,如果在异步上下文(await)中使用,需要非常谨慎. 本文将说一个在同步上下文中非常常 ...

  3. .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况

    一个简单的 Task 不会消耗多少时间,但如果你不合适地将 Task 转为同步等待,那么也可能很快耗尽线程池的所有资源,出现类似死锁的情况. 本文将以一个最简单的例子说明如何出现以及避免这样的问题. ...

  4. C#线程 使用线程

    第三部分 使用线程 基于事件的异步模式 基于事件的异步模式(EAP)提供了一种简单的方法,通过这些方法,类可以提供多线程功能,而使用者无需显式启动或管理线程.它还提供以下功能: 合作取消模型 工作人员 ...

  5. Kotlin for Java Developers 学习笔记

    Kotlin for Java Developers 学习笔记 ★ Coursera 课程 Kotlin for Java Developers(由 JetBrains 提供)的学习笔记 " ...

  6. Dispatcher中Invoke与BeginInvoke

    [同步]Invoke Application.Current.Dispatcher.Invoke(AutoIncreaseNumber); [异步]BeginInvoke Application.Cu ...

  7. 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)

    深耕 WPF 开发的各位程序员大大们一定避不开使用 Dispatcher.跨线程访问 UI 当然免不了用到它,将某个任务延迟到当前任务之后执行也会用到它.Dispatcher.Invoke.Dispa ...

  8. WPF入门教程系列四——Dispatcher介绍

    一.Dispatcher介绍 微软在WPF引入了Dispatcher,那么这个Dispatcher的主要作用是什么呢? 不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以 ...

  9. WPF中的Invoke

    今天帮同事看一个问题,她用为了实现动画效果用主线程执行Thread.Sleep,然后界面就卡死了. 这个问题好解决,new 一个Thread就行了,但是更新WPF的界面需要主线程的操作,然后习惯性的打 ...

随机推荐

  1. RabbitMQ入门_13_消息持久化

    参考资料:https://www.rabbitmq.com/tutorials/tutorial-two-java.html 默认情况下,队列中的消息是不持久化的.如果 RabbitMQ 崩溃,队列中 ...

  2. cocos2dx 在windows下开启console

    cocos2dx 3.10 1.在main函数中写入代码 AllocConsole(); freopen("CONIN$", "r", stdin); freo ...

  3. Codeforces 862C - Mahmoud and Ehab and the xor

    862C - Mahmoud and Ehab and the xor 思路:找两对异或后等于(1<<17-1)的数(相当于加起来等于1<<17-1),两个再异或一下就变成0了 ...

  4. React生命周期钩子

    最近的工作都很忙,所以很少完整的时间可以用来总结和回顾知识点,今天就趁着是周末,我准备在这里复习和回顾一下React的基础.工作中主要用的vue比较多,在工作中使用React也已经是一年前了,当时用的 ...

  5. 慕课网Hibernate初探之一对多映射实验及总结

    慕课网Hibernate初探之一对多映射实验及总结 一.本课核心 * 1.如何在MyEclipse中使用Hibernate * 2.如何实现Hibernate中一对多的映射 * 3.如何创建Sessi ...

  6. jsp / get 中文乱码问题

    POST 方式下的解决方式还算简单,因为POST 方式下提交的数据都是以二进制的方式附加在http请求的body部分发送,只需要在后台指定编码格式就足矣解决. GET 方式下会将参数直接附加到url ...

  7. spoj The Next Palindrome

    题意:比给出的数大的最小回文数. 先用前n/2长对称到后面,如果没变大,在中间加1,进位,再对称. //#pragma comment(linker,"/STACK:1024000000,1 ...

  8. ural Ambitious Experiment 树状数组

    During several decades, scientists from planet Nibiru are working to create an engine that would all ...

  9. 基于GUI的简单聊天室01

    运用了Socket编程,gui,流的读入和写出,线程控制等 思路: 1.首先是在客户端中先建立好聊天的GUI 2.建立服务器端,设置好端口号(用SocketServer),其中需要两个boolean变 ...

  10. 【python】判断值是否在list和set的对比以及set的实现原理

    判断值是否在set集合中的速度明显要比list快的多, 因为查找set用到了hash,时间在O(1)级别. 假设listA有100w个元素,setA=set(listA)即setA为listA转换之后 ...