不要使用 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的界面需要主线程的操作,然后习惯性的打 ...
随机推荐
- Android GridView 分页加载数据
android UI 往右滑动,滑动到最后一页就自动加载数据并显示 如图: package cn.anycall.ju; import java.util.ArrayList; import java ...
- angular组件层次与军事指挥层级职责的联系
又继续读angular文档,发现自己之前理解还是有误.按官方文档的思路service不是属于component的,是属于module的.module才是负责完整领域逻辑的单位.demo的英雄编辑器给我 ...
- C#通过Oracle.ManagedDataAccess无法访问Oralce
问题描述:通过C#引用Oracle.ManagedDataAccess.dll访问Oracle,写了如下一段代码,在本机能正常访问,但是将编译后的exe放到服务器上面就无法访问了,一直提示登录失败.而 ...
- Java实例-坦克大战
Java实例-坦克大战 一.样例图片 二.类图结构 坦克大战中的所有类 类的关系图 我的坦克类 三.说明 1.每一个新的独立运行的东西就是一个线程,像我方坦克,像敌方坦克,像所有的子弹 2.每一个线程 ...
- JDK1.5 新特性
1:自动装箱与拆箱 自动装箱:每当需要一种类型的对象时,这种基本类型就自动地封装到与它相同类型的包装中. 自动拆箱:每当需要一个值时,被装箱对象中的值就被自动地提取出来,没必要再去调用intValue ...
- stl算法:next_permutation剖析
在标准库算法中,next_permutation应用在数列操作上比较广泛.这个函数可以计算一组数据的全排列.但是怎么用,原理如何,我做了简单的剖析. 首先查看stl中相关信息.函数原型: templa ...
- 关于floyd 打印路径的问题
我们令 f[i][j] 表示从 i-->j的最短路上j前面的那个点. 显然初始化时 f[i][j]=i; (这样的话先判断一下i是否能到达j好点) 更新条件时,当发现通过点k能使最短 ...
- Oracle 11g dataguard check RTA(real time apply)
Oracle 11g dataguard check RTA(real time apply) 2017年8月24日 16:38 环境:oracle 11.2.0.1 OEL 5.8 注:以下操作都在 ...
- yield 关键字
yield 关键字向编译器指示它所在的方法是迭代器块.编译器生成一个类来实现迭代器块中表示的行为.在迭代器块中,yield 关键字与 return 关键字结合使用,向枚举器对象提供值.这是一个返回值, ...
- 84. Largest Rectangle in Histogram *HARD* -- 柱状图求最大面积 85. Maximal Rectangle *HARD* -- 求01矩阵中的最大矩形
1. Given n non-negative integers representing the histogram's bar height where the width of each bar ...