异步陷阱之死锁篇

简介:

提倡异步编程旨在给用户更好的前端体验,但异步编程也让学习成本和犯错几率大大升高,其中最常见且最难处理的就是死锁。

何谓“死锁”,英文术语称“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”: 
Deadlock-Run   
这是一个不同次序请求加锁导致死锁,归功于我们的教材对此类死锁的解释非常详细,这里我一笔带过,接下来看看日常开发中经常遇到的一些更具体的死锁情况——线程死锁。

场景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线程锁:主执行线程调用子线程后挂起等待子线程结果,子线程又需要切换到主线程或者等待主线程返回,从而导致两个线程均处在阻塞状态(死锁),如下图所示:

 deadlock 

解决方案很简单,去除所有的同步等待,至少确保在主线程上一定不要使用同步等待,如何操作呢?你可以到多种选择,这里我提几点,抛砖引玉,希望大家可以在实际应用中或者更多灵感和解决方法。

1、去除所有wait,使用async和await关键字重写,推荐使用。    
这里或许你会有些迷惑,为什么async和await就能保证不会线程死锁呢?如下图示意代码片段,当前线程执行完(1)之后,接着执行(2),注意这里执行(2)会切换线程,但是不是阻塞当前线程,.NET在这里耍了个“花招”,实际编译器发现async和await关键字的时候会自动插入一些代码,利用状态机在(3)的位置做了个标记,让当前线程“飞”了一会,等到await所处的子线程结束的时候,修改状态机状态,让当前线程恢复到(3)这里,接着就可以跑(4),从开发者的角度来看,好像这一段代码是顺序执行的。重要的是,这里没有wait锁。

async-await

2、去除所有wait,使用Task.ContinueWith来实现代码顺序。    
var ta = new Task(()=>{ doSome(); });    
ta.ContinueWith((tc)=>{ doAnother(tc.Result); });

3、去除所有wait,将wait之后的代码移到单独的调用中,使用事件或者回调函数的方式,在子线程结束的时候,激活主线程。以WinForm为例,如下图所示:    
winform%20async

附上文中所提到测试的代码工程:下载地址



本文转自 powertoolsteam 51CTO博客,原文链接:http://blog.51cto.com/powertoolsteam/1553183,如需转载请自行联系原作者

相关文章
|
监控 安全 BI
医院不良事件管理系统,PHP不良事件系统源代码
医院不良事件管理系统(HAEMS)是医院质量管理体系的核心,用于系统化收集、报告、分析和处理各类不良事件及近似差错,以提升患者安全和运营效率。系统涵盖事件报告、调查分析、改进追踪、统计分析及知识库管理等功能模块,支持多渠道上报、根本原因分析(RCA)、改进措施闭环管理及多维度数据分析。同时,系统注重用户体验与数据安全,符合医疗行业法规标准,通过标准化接口实现与其他系统的无缝集成。HAEMS不仅是工具,更是推动医院安全文化与持续质量改进的核心引擎,助力构建更安全的医疗环境。
468 0
|
存储 编解码 算法
视频编码格式和封装格式有什么关系?相机常见的编码格式有哪些?
视频编码格式与封装格式的关系类似于酒与酒瓶的关系。编码格式是视频的核心内容,如H.264、H.265等,而封装格式则是将视频、音频、字幕等集成在一起的外壳,如MP4、MKV等。不同的封装格式适应不同的播放需求,例如MP4兼容性最好,MKV适合网络传播。
|
安全 搜索推荐 生物认证
FOFA基础和使用技巧
FOFA基础和使用技巧
|
机器学习/深度学习 算法
黑盒攻击中迁移攻击和通用对抗扰动的讲解及实战(附源码)
黑盒攻击中迁移攻击和通用对抗扰动的讲解及实战(附源码)
798 1
|
Ubuntu 搜索推荐 数据挖掘
下载Ubantu镜像文件、创建虚拟机以及ubantu安装详细教程(系统性学习day1)
下载Ubantu镜像文件、创建虚拟机以及ubantu安装详细教程(系统性学习day1)
QGS
|
Android开发
JavaFX场景入门(上)
JavaFX场景入门
QGS
540 0
|
消息中间件 存储 网络协议
MQ - 09 RabbitMQ的架构设计与实现
MQ - 09 RabbitMQ的架构设计与实现
1240 0
|
语音技术 Python
Python 技术篇-1行代码实现语音识别,speech库快速实现简单的语音对话
Python 技术篇-1行代码实现语音识别,speech库快速实现简单的语音对话
1270 0
Python 技术篇-1行代码实现语音识别,speech库快速实现简单的语音对话
|
JSON 关系型数据库 MySQL
[Database] MySQL 5.7+ JSON 字段的使用的处理
[Database] MySQL 5.7+ JSON 字段的使用的处理
425 0
[Database] MySQL 5.7+ JSON 字段的使用的处理
|
移动开发 前端开发 JavaScript
通过游戏学javascript系列第一节Canvas游戏开发基础
通过游戏学javascript系列第一节Canvas游戏开发基础
709 0