不要使用 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的界面需要主线程的操作,然后习惯性的打 ...
随机推荐
- OpenGL入门程序五:三维绘制
1.现实世界观察一个物体的时候,可能涉及到的三维变化: 1>视图变化------从不同的角度观察. 2>模型变化------移动.旋转物体,计算机中当然还可以对物体进行缩放. 3>投 ...
- English trip -- VC(情景课) 6 C Is your class at 11:00? 你的课11点开始吗?
Grammar focus 语法点 Is your class at 11:00 ? # 带be动词的一般疑问句 Yes, it is No, it isn't 相当于 is not Pra ...
- 『Sklearn』框架自带数据集接口
自带数据集类型如下: # 自带小型数据集# sklearn.datasets.load_<name># 在线下载数据集# sklearn.datasets.fetch_<name&g ...
- HBase基于region数目和data locality来balance regions
1. 在Hbase的运维过程中,我们经常需要做如下操作: 移动 regionserver 到其他的 regionserver group中 下线一台机器 增加一台机器 移动 table 到其他 re ...
- hdu 1226 bfs+余数判重+大数取余
题目: 超级密码 Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total ...
- Leetcode 17
//狂练回溯,巧用备胎class Solution { public: vector<string> letterCombinations(string digits) { vector& ...
- git在eclipse中的配置 转载
git在eclipse中的配置 转载 一_安装EGIT插件 http://download.eclipse.org/egit/updates/ 或者使用Eclipse Marketplace,搜索EG ...
- 最新的ES 5.0路由算法底层实现
http://www.cnblogs.com/bonelee/p/6078947.html 里分析了ES bulk实现,其中路由代码: ShardId shardId = clusterService ...
- java并发编程:线程安全管理类--原子操作类--AtomicReference<V>
1.类 AtomicReference<V> public class AtomicReference<V>extends Objectimplements Serializa ...
- js常用用途
页面元素查找 查找元素方法表达式 返回值 依据元 ...