异步陷阱之死锁篇

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

何谓“死锁”,英文术语称“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为例,如下图所示: ​

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菩提树下的杨过

.net中的认证(authentication)与授权(authorization)

注:这篇文章主要给新手看的,老手们可能会觉得没啥营养,就请绕过吧。 “认证”与“授权”是几乎所有系统中都会涉及的概念,通俗点讲: 认证(authenticat...

377100
来自专栏jessetalks

一不小心写了个WEB服务器

开场   Web服务器是啥玩意? 是那个托管了我的网站的机器么? No,虽然那个也是服务器,但是我们今天要说的Web服务器主要是指像IIS这样一类的,用于处理r...

45750
来自专栏大内老A

如何通过自定义MessageFilter的方式利用按键方式操作控件滚动条[附源代码]

很长一段时间内,一直在做一个SCSF(Smart Client Software Factory)的项目,已经进入UAT阶段。最近,用户提出了一个要求:需要通过...

31070
来自专栏恰童鞋骚年

自己动手模拟开发一个简单的Web服务器

开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此...

25630
来自专栏逸鹏说道

Python3 与 C# 并发编程之~ 上篇

其实逆天现在Coding已经是80%变成Python了,20%才是Net,也不确定是否一直在Net界干下去,所以只能尽可能的在说新知识的同时,尽量把脑子里面Ne...

13540
来自专栏NetCore

【翻译】在Visual Studio中使用Asp.Net Core MVC创建你的第一个Web API应用(一)

HTTP is not just for serving up web pages. It’s also a powerful platform for bui...

26450
来自专栏菩提树下的杨过

用VS2010调试微软开放的部分源码

msdn上有一篇讲解如何用vs2008调试源码的文章:http://blogs.msdn.com/b/sburke/archive/2008/01/16/con...

23750
来自专栏.NET开发者社区

C#/.NET RestSharp网络组件实现上传文件到远程服务器【可跨域传文件】

以前给大家分享了一个C#/.NET的网络组件–RestSharp,具体请参考:推荐一个.NET(C#)的HTTP辅助类组件–restsharp 今天再给大家示...

403100
来自专栏个人随笔

体检套餐管理系统 -- List<T>单列集合

本文章为List<T>单列集合开发项目,如需要 Dictionary<K,V>双列集合开发的此项目,请到楼主博客园寻找 博客网址:http://www.cnbl...

35570
来自专栏me的随笔

ASP.NET MVC5请求管道和生命周期

请求管道是一些用于处理HTTP请求的模块组合,在ASP.NET中,请求管道有两个核心组件:IHttpModule和IHttpHandler。所有的HTTP请求都...

15230

扫码关注云+社区

领取腾讯云代金券