不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 Lazy 中导致死锁
WPF 中为了 UI 的跨线程访问,提供了 Dispatcher 线程模型。其 Invoke 方法,无论在哪个线程调用,都可以让传入的方法回到 UI 线程。
然而,如果你在 Lazy 上下文中使用了 Invoke,那么当这个 Lazy<T> 跨线程并发时,极有可能导致死锁。本文将具体说说这个例子。
一段死锁的代码
请先看一段非常简单的 WPF 代码:
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;- 后台线程和主 UI 线程并发访问这个
Lazy<T>,且后台线程先于主 UI 线程访问这个Lazy<T>; Lazy<T>内部的代码包含主线程的Invoke。
此死锁的原因
- 后台线程访问到 Lazy,于是 Lazy 内部获得同步锁;
- 主 UI 线程访问到 Lazy,于是主 UI 线程等待同步锁完成,并进入阻塞状态(以至于不能处理消息循环);
- 后台线程的初始化调用到
Invoke需要到 UI 线程完成指定的任务后才会返回,但 UI 线程此时阻塞不能处理消息循环,以至于无法完成Invoke内的任务;
于是,后台线程在等待 UI 线程处理消息以便让 Invoke 完成,而主 UI 线程由于进入 Lazy 的等待,于是不能完成 Invoke 中的任务;于是发生死锁。
此死锁的解决方法
Invoke 改为 InvokeAsync 便能解锁。
这么做能解决的原因是:后台线程能够及时返回,这样 UI 线程便能够继续执行,包括执行 InvokeAsync 中传入的任务。
实际上,以上可能是最好的解决办法了。因为:
- 我们使用 Lazy 并且设置线程安全,一定是因为这个初始化过程会被多个线程访问;
- 我们会在 Lazy 的初始化代码中使用回到主线程的
Invoke,也是因为我们预料到这份初始化代码可能在后台线程执行。
所以,这段初始化代码既然不可避免地会并发,那么就应该阻止并发造成的死锁问题。也就是不要使用 Invoke 而是改用 InvokeAsync。
如果需要使用 Invoke 的返回值,那么改为 InvokeAsync 之后,可以使用 await 异步等待返回值。
更多死锁问题
死锁问题:
- 使用 Task.Wait()?立刻死锁(deadlock) - walterlv
- 不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 Lazy 中导致死锁 - walterlv
- 在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁
- .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况 - walterlv
解决方法:
- 在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁 - walterlv
- 将 async/await 异步代码转换为安全的不会死锁的同步代码(使用 PushFrame) - walterlv
不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 Lazy 中导致死锁的更多相关文章
- Dispatcher.Invoke方法
前一篇小猪分享过在WPF中简单的使用BackgroundWorker完成多线程操作!在那篇中小猪利用了BackgroundWorker组件对耗时比较多的操作放在了单独的BackgroundWorker ...
- 在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁
AutoResetEvent.ManualResetEvent.Monitor.lock 等等这些用来做同步的类,如果在异步上下文(await)中使用,需要非常谨慎. 本文将说一个在同步上下文中非常常 ...
- .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
一个简单的 Task 不会消耗多少时间,但如果你不合适地将 Task 转为同步等待,那么也可能很快耗尽线程池的所有资源,出现类似死锁的情况. 本文将以一个最简单的例子说明如何出现以及避免这样的问题. ...
- C#线程 使用线程
第三部分 使用线程 基于事件的异步模式 基于事件的异步模式(EAP)提供了一种简单的方法,通过这些方法,类可以提供多线程功能,而使用者无需显式启动或管理线程.它还提供以下功能: 合作取消模型 工作人员 ...
- Kotlin for Java Developers 学习笔记
Kotlin for Java Developers 学习笔记 ★ Coursera 课程 Kotlin for Java Developers(由 JetBrains 提供)的学习笔记 " ...
- Dispatcher中Invoke与BeginInvoke
[同步]Invoke Application.Current.Dispatcher.Invoke(AutoIncreaseNumber); [异步]BeginInvoke Application.Cu ...
- 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)
深耕 WPF 开发的各位程序员大大们一定避不开使用 Dispatcher.跨线程访问 UI 当然免不了用到它,将某个任务延迟到当前任务之后执行也会用到它.Dispatcher.Invoke.Dispa ...
- WPF入门教程系列四——Dispatcher介绍
一.Dispatcher介绍 微软在WPF引入了Dispatcher,那么这个Dispatcher的主要作用是什么呢? 不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以 ...
- WPF中的Invoke
今天帮同事看一个问题,她用为了实现动画效果用主线程执行Thread.Sleep,然后界面就卡死了. 这个问题好解决,new 一个Thread就行了,但是更新WPF的界面需要主线程的操作,然后习惯性的打 ...
随机推荐
- Python 爬虫-信息的标记xml,json,yaml
2017-07-26 23:53:03 信息标记的作用有: 标记后的信息可形成信息组织结构,增加了信息维度 标记的结构与信息一样具有重要价值 标记后的信息可用于通信.存储或展示 标记后的信息更利于程 ...
- C++STL3--queue
C++STL3--queue 一.心得 STL的这些东西用法都差不多 二.介绍 queue数据结构中的队列 priority_queue优先队列,插入进去的元素都会从大到小排好序 PS:在priori ...
- javascript之构造函数的继承(引用网络)
这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例. 今天要介绍的是,对象之间的"继承"的五种方法. 比如,现在有一个" ...
- English trip -- VC(情景课) 7 A Shopping 购物
Words The clothes place a dress 长裙 short skirt 短裙 pants 裤子 / trousers 长裤 / shorts 短裤 a shir ...
- Mvc 学习笔记(一)
1. MVC 表示 模型-视图-控制器.MVC是一种用于开发应用程序的模式,具备良好的架构,可测试和易于维护.基于MVC应用程序中包含: Models:表示应用程序的数据,并使用验证逻辑强制执行业务规 ...
- 使用poi导出Excel,并设定单元格内容类型,抛出异常
本例子使用的是HSSF,为Excel2003提供处理方案. 设定为输入类型为数值 import org.apache.poi.hssf.usermodel.DVConstraint; import o ...
- learning docker steps(3) ----- docker services 初次体验
参考:https://docs.docker.com/get-started/part3/#docker-composeyml docker 的 service样例, 我们可以理解成是一个本地负载均衡 ...
- Microsoft Office相关开发组件
安装office,直接引用COM控件 C#4提供对PIA引用的一种方式:链接(编译器只会将PIA中需要的部分直接嵌入到程序集中),变体(variant)被视为动态类型,以减少强制转换需要的开销: 不安 ...
- TypeScript安装
1.Installing Node.js and updating npm Updating npm----npm install npm@latest -g Installing TypeScrip ...
- linux目录管理、时钟管理、文件查看命令
inux的两种时钟: 系统时钟:由Linux内核通过CPU的工作频率进行的计时: 硬件时钟: hwclock: 显示硬件时钟 -s, --hctosys -w, --systohc cal:日历 目录 ...