异步陷阱之死锁篇
来源:互联网 发布:java覆盖父类方法 编辑:程序博客网 时间:2024/06/05 16:12
提倡异步编程旨在给用户更好的前端体验,但异步编程也让学习成本和犯错几率大大升高,其中最常见且最难处理的就是死锁。
何谓“死锁”,英文术语称“Deadlock”,当两个以上的运算单元,双方都在等待对方停止运行,以取得系统资源,但是没有一方提前退出时,这种状况,就称为死锁。
举个例子吧,这里是一段经典的死锁示例代码:
int sharedResource1 = 1, sharedResource2 = 2;var lockResource1 = newobject();var lockResource2 = newobject();var t1 = newThread(() =>{ Console.WriteLine("thead 1 begin"); lock (lockResource1) { Thread.Sleep(10); lock (lockResource2) { sharedResource1++; sharedResource2++; } } Console.WriteLine("thead 1 end");});var t2 = newThread(() =>{ Console.WriteLine("thead 2 begin"); lock (lockResource2) { Thread.Sleep(10); lock (lockResource1) { sharedResource1++; sharedResource2++; } } Console.WriteLine("thead 2 end");});t1.Start();t2.Start();
运行结果如下,永远也不会看到“thread x end”:
这是一个不同次序请求加锁导致死锁,归功于我们的教材对此类死锁的解释非常详细,这里我一笔带过,接下来看看日常开发中经常遇到的一些更具体的死锁情况——线程死锁。
场景1—Task之间互相等待导致死锁:
Task t1 = null, t2 = null;t1 = Task.Factory.StartNew(() =>{ Console.WriteLine("task 1 begin"); Task.Delay(10); Task.WaitAll(t2); Console.WriteLine("task 1 end");});t2 = Task.Factory.StartNew(() =>{ Console.WriteLine("task 2 begin"); Task.Delay(10); Task.WaitAll(t1); Console.WriteLine("task 2 end");});Task.WaitAll(t1, t2);Console.WriteLine("Done");
场景2—WinForm Invoke抢夺UI线程死锁:
privatevoid button1_Click(object sender, EventArgs e){ var t = Task.Factory.StartNew<string>(() => { Thread.Sleep(0); var text = Invoke(newFunc<string>(() => { // do some ui-dependent works return Text; })); return text + " - new title"; }); Text = t.Result;}
场景3—WPF Dispatcher切换死锁
privatevoid Button_Click(object sender, RoutedEventArgs e){ var t = Task.Factory.StartNew<Brush>((state) => { Task.Delay(10); var clr = (Color)newColorConverter() .ConvertFromInvariantString(state asstring); var brush = Dispatcher.Invoke<SolidColorBrush>(() => { // do some works returnnewSolidColorBrush() { Color = clr }; }); return brush; }, "red"); theButton.Background = t.Result;}
这里将各种无关代码精简筛除,基本上很快就可以发现这些情况中的问题,是的,实际上以上几种场景均是同一个原因——wait线程锁:主执行线程调用子线程后挂起等待子线程结果,子线程又需要切换到主线程或者等待主线程返回,从而导致两个线程均处在阻塞状态(死锁),如下图所示:
解决方案很简单,去除所有的同步等待,至少确保在主线程上一定不要使用同步等待,如何操作呢?你可以到多种选择,这里我提几点,抛砖引玉,希望大家可以在实际应用中或者更多灵感和解决方法。
1、去除所有wait,使用async和await关键字重写,推荐使用。
这里或许你会有些迷惑,为什么async和await就能保证不会线程死锁呢?如下图示意代码片段,当前线程执行完(1)之后,接着执行(2),注意这里执行(2)会切换线程,但是不是阻塞当前线程,.NET在这里耍了个“花招”,实际编译器发现async和await关键字的时候会自动插入一些代码,利用状态机在(3)的位置做了个标记,让当前线程“飞”了一会,等到await所处的子线程结束的时候,修改状态机状态,让当前线程恢复到(3)这里,接着就可以跑(4),从开发者的角度来看,好像这一段代码是顺序执行的。重要的是,这里没有wait锁。
2、去除所有wait,使用Task.ContinueWith来实现代码顺序。
var ta = new Task(()=>{ doSome(); });
ta.ContinueWith((tc)=>{ doAnother(tc.Result); });
3、去除所有wait,将wait之后的代码移到单独的调用中,使用事件或者回调函数的方式,在子线程结束的时候,激活主线程。以WinForm为例,如下图所示:
附上文中所提到测试的代码工程:下载地址
- 异步陷阱之死锁篇
- 异步陷阱之IO篇
- 异步陷阱之IO篇
- HttpContext.Current:异步模式下的疑似陷阱之源
- 互斥锁陷阱:优先级反转、死锁
- C#中的异步陷阱
- C陷阱篇之enum默认长度
- C陷阱篇之define的缺陷
- C陷阱篇之移位运算
- C陷阱篇之运算符优先级
- C陷阱篇之常见手误
- 操作系统之线程篇3死锁
- 操作系统养成计划之篇二:死锁
- node异步读取文件的陷阱
- Node.js For 循环 异步陷阱
- 第三方支付异步通知的陷阱
- C陷阱与缺陷之词法陷阱
- C陷阱与缺陷之语法陷阱
- 【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对比度
- T行业资讯每周汇报:的萨达视媒体广告花费增长放缓
- 第二章>IOC基础、容器原理和IOC配置
- iOS中arc的设置与使用
- js 数组的使用总结
- 异步陷阱之死锁篇
- git,github在windows上的搭建
- TCP,UDP伪首部描述
- MATLAB subplot显示不同尺度图像
- Objective-c的@property 详解
- LeetCode-Valid Sudoku
- HDU 5012 BFS水
- 撒打发的更好分割好飞规划局规划局刚刚好名女兵表V刹被拆除vb承包vb
- Android之Animation动画使用技巧