不要使用 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入门程序一:绘制简单的矩形
#include <GL/glut.h> void MyDisplay(void); int main(int argc, char **argv) { //设置窗口的大小 glutIni ...
- python模块——re模块(简单的计算器功能实现_eval版)
#!/usr/bin/env python # -*- coding:utf-8 -*- __author__ = "loki" # Usage: Make a Diy Calcu ...
- codeforces 55d//Beautiful numbers// Codeforces Beta Round #51
题意:一个数能整除它所有的位上的数字(除了0),统计这样数的个数. 注意离散化,为了速度更快需存入数组查找. 不要每次memset,记录下已有的长度下符合条件的个数. 数位dp肯定是从高位到低位. 记 ...
- python-day43--多表查询
一.多表连接查询: #重点:外链接语法 准备表 #建表 create table department( id int, name varchar(20) ); create table ...
- DIV字体
1.如何设定文字字体.颜色.大小 —— 使用font font-style设定斜体,比如font-style: italicfont-weight设定文字粗细,比如font-weight: bold; ...
- SSH 远程端口转发
既然"本地端口转发"是指绑定本地端口的转发,那么"远程端口转发"(remote forwarding)当然是指绑定远程端口的转发. 还是接着看上面那个例子,ho ...
- JavaScript学习总结(三)——逻辑And运算符详解
在JavaScript中,逻辑 AND 运算符用双和号(&&)表示 1 var bTrue = true; 2 var bFalse = false; 3 var bResult = ...
- elasticsearch配置详解
一.说明 使用的是新版本5.1,直接从官网下载rpm包进行安装,https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5 ...
- JdbcTemplate.queryForObject
} catch (EmptyResultDataAccessException e) { log.info(">>>检测到有无记录>>>>" ...
- UVALive 5840 数学题
DES:给出三种材料A,B,C每种的个数.然后组合AB,BC,AC的利润.问能获得的最大利润是多少. 开始一点思路都没有.然后发现可以枚举其中两种的个数.那么最后一种就确定了.还是感觉很机智. #in ...