可能发生死锁的程序类型

1、WPF/WinForm程序

2、asp.net (不包括asp.net core)程序

死锁的产生原理

对异步方法返回的Task调用Wait()或访问Result属性时,可能会产生死锁。

下面的WPF代码会出现死锁:

        private void Button_Click_7(object sender, RoutedEventArgs e)
{
Method1().Wait();
} private async Task Method1()
{
await Task.Delay(); txtLog.AppendText("后续代码");
}

下面的asp.net mvc代码也会出现死锁:

        public ActionResult Index()
{
string s=Method1().Result; return View();
} private async Task<string> Method1()
{
await Task.Delay(); return "hello";
}

以WPF代码为例,事件处理器调用Method1,得到Task对象,然后调用Task的Wait方法,阻塞自己所在的线程,即主线程,直到Task对象“完成”。而返回的Task对象要想“完成”,必须在主线程上执行await之后的代码。而主线程早就处于阻塞状态,它在等待Task对象完成!于是死锁就产生了。

asp.net mvc代码是同样的道理。

什么时候必然会死锁,如何避免

从上面的两个例子中似乎可以得出结论:在WPF/WinForm/asp.net程序中,在异步方法上调用.Result/Wait(),就会产生死锁。然而事实并非如此。

如下面的WPF代码就不会出现死锁:(从web获取数据并显示在文本框中。此代码仅为举例说明,异步事件处理器才是正道)

        private void Button_Click_8(object sender, RoutedEventArgs e)
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://www.baidu.com/"); string html = httpClient.GetStringAsync("/").Result; html = "【" + html + "】"; txtLog.AppendText(html);
}

把获取数据的代码摘出来吧:

        private void Button_Click_8(object sender, RoutedEventArgs e)
{
string html = GetHtml(); txtLog.AppendText(html);
} private string GetHtml()
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://www.baidu.com/"); string html=httpClient.GetStringAsync("/").Result;        html = "【" + html + "】";
       
return html;
}

完全没问题,这是肯定的。

GetHtml()可以写成异步方法,再改一下:

        private void Button_Click_8(object sender, RoutedEventArgs e)
{
string html = GetHtml().Result; txtLog.AppendText(html);
} private async Task<string> GetHtml()
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://www.baidu.com/");
            string html=await httpClient.GetStringAsync("/");              
            html = "【" + html + "】";
           return html;
}

(HttpClient的GetStringAsync()方法是异步方法,我们调用它,然后用async/await的方式创建了一个自己的异步方法。先不“一路异步到底(Async All the Way)”。)

运行一下,死锁出现了。

为什么在HttpClient的GetStringAsync()方法上执行.Result不会死锁,而在自己写的异步方法上执行.Result,就出现了死锁?难道HttpClient的GetStringAsync()方法内部有什么特殊的处理?

看一下mono的HttpClient源代码,可以发现:

所有await 表达式后面,都加了ConfigureAwait (false),如

return await resp.Content.ReadAsStringAsync ().ConfigureAwait (false);

而由Task的msdn文档可以知,ConfigureAwait (false)会指示await之后的代码不在原先的context (可理解为线程)上运行。

修改一下GetHtml()异步方法的代码:

        private void Button_Click_8(object sender, RoutedEventArgs e)
{
string html = GetHtml().Result; txtLog.AppendText(html);
}
        private async Task<string> GetHtml()
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://www.baidu.com/");
            string html=await httpClient.GetStringAsync("/").ConfigureAwait(false);              
            html = "【" + html + "】";
           return html;
}
 

可以发现,死锁不会出现了。

分析:GetHtml()被调用后,主线程阻塞,等待Task对象“完成”;HttpClient获取数据完毕,在另外的线程上执行了await的之后的代码,于是Task对象完成。主线程恢复执行。(注意,即使“await之后没有代码”,即GetHtml()方法体中直接写return await httpClient.GetStringAsync("/"),也是需要加.ConfigureAwait(false)的)

当然,如果事件处理器是异步的,即使不加.ConfigureAwait(false),也不会有任何问题:

        private async void Button_Click_8(object sender, RoutedEventArgs e)
{
string html = await GetHtml(); txtLog.AppendText(html);
}
private async Task<string> GetHtml()
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://www.baidu.com/");
string html = await httpClient.GetStringAsync("/");
html = "【" + html + "】";
return html;
}

试想一下,如果GetHtml()被放到单独的类中,做成类库,那么,里面如果不加.ConfigureAwait(false),则只能假设使用这个类库的人严格遵循异步编程规范了。一旦使用者在GetHtml()上执行.Result,死锁就无可避免了。

仔细看HttpClient的源代码,可以发现,它的GetStringAsync()方法也并不是“天生的”异步方法,它也是用await运算符调用了自己的其他的异步方法,并且在每次调用后都添加了.ConfigureAwait(false)。

那么,最初的WPF程序的死锁是否可以用.ConfigureAwait(false)解决呢?注意,txtLog是一个文本框,UI控件只能在UI线程访问,所以添加上.ConfigureAwait(false)后会报错:“InvalidOperationException: 调用线程无法访问此对象,因为另一个线程拥有该对象”。那么是否可以改成这样:

        private void Button_Click_7(object sender, RoutedEventArgs e)
{
Method1().Wait();
} private async Task Method1()
{
await Task.Delay().ConfigureAwait(false); Dispatcher.Invoke(() => {
txtLog.AppendText("后续代码");
});
}

依然是死锁。所以,乖乖的用异步事件处理器吧:

        private async void Button_Click_7(object sender, RoutedEventArgs e)
{
await Method1();
} private async Task Method1()
{
await Task.Delay(); txtLog.AppendText("后续代码");
}

上面的代码还说明一个问题:在异步工具方法中,不要写访问UI控件的代码,否则无法规避死锁问题。

总结

  1. 死锁会发生在不遵循异步编程规范——在异步方法返回的Task对象上执行Wait()或.Result时

  2. ConfigureAwait(false)指定await后的代码不返回原先的context,可以避免死锁
  3. 如果await之后的代码不需要返回原先的context执行,例如,仅仅是执行Http请求,获取和处理数据,那么完全可以加上ConfigureAwait(false)。

  4. 如果作为类库的创作者,编写异步方法时,应尽可能的使用ConfigureAwait(false),以保证一旦类库的使用者阻塞异步方法时,不会产生死锁。
  5. 在异步类库/工具方法中,应避免加入访问UI控件的代码

附加  async/await学习资料

C# Under the Hood: async/await  作者从动手写一个“可等待”的方法开始,进而通过反编译工具分析异步方法生成的的实质代码,揭示了async/await的本质——回调

What happens in an async method  msdn编程指南,图示异步方法的执行流程

C# async await 死锁问题总结的更多相关文章

  1. [译]async/await中阻塞死锁

    这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的两篇博文中翻译过来. 原文1:Don'tBlock o ...

  2. [译]async/await中使用阻塞式代码导致死锁 百万数据排序:优化的选择排序(堆排序)

    [译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的 ...

  3. 将 async/await 异步代码转换为安全的不会死锁的同步代码

    在 async/await 异步模型(即 TAP Task-based Asynchronous Pattern)出现以前,有大量的同步代码存在于代码库中,以至于这些代码全部迁移到 async/awa ...

  4. async/await 的一些知识 (死锁问题)

    博文 Don't Block on Async Code What is the purpose of "return await" in C#? Any difference b ...

  5. [译]async/await中使用阻塞式代码导致死锁

    原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Clea ...

  6. C# Async, Await and using statements

    Async, Await 是基于 .NEt 4.5架构的, 用于处理异步,防止死锁的方法的开始和结束, 提高程序的响应能力.比如: Application area           Support ...

  7. Async/Await 最佳实践

    其实好久以前就看过这个文章,以及类似的很多篇文章.最近在和一个新同事的交流中发现原来对async的死锁理解不是很透彻,正好最近时间比较充裕就再当一回搬运工. 本文假定你对.NET Framework ...

  8. 译文: async/await SynchronizationContext 上下文问题

    async / await 使异步代码更容易写,因为它隐藏了很多细节. 许多这些细节都捕获在 SynchronizationContext 中,这些可能会改变异步代码的行为完全由于你执行你的代码的环境 ...

  9. async/await的多线程问题

    今天尝试把.net4.5新增的异步编程模型async/await加入自己的框架,因为从第一印象看,使用async/await的写法实在太方便了,以同步代码的方式写异步流程,写起来更顺畅,不容易打断思路 ...

随机推荐

  1. @bzoj - 3750@ [POI2015] Pieczęć

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 一张 n*m 的方格纸,有些格子需要印成黑色,剩下的格子需要保留 ...

  2. HDU 1864 01背包、

    这题题意有点坑阿.感觉特别模糊. 我开始有一点没理解清楚.就是报销的话是整张整张支票报销的.也是我傻逼了 没一点常识 还有一点就是说单张支票总额不超过1000,每张支票中单类总额不超过600,我开始以 ...

  3. Pytorch使用GPU

    pytorch如何使用GPU在本文中,我将介绍简单如何使用GPU pytorch是一个非常优秀的深度学习的框架,具有速度快,代码简洁,可读性强的优点. 我们使用pytorch做一个简单的回归. 首先准 ...

  4. 最小生成树prim、

    过年那几天确实没好好学习.在老家闲着也是闲着.可是就是没看书. 回来这几天又一直在弄个人博客.买域名云服务器备案什么的- -. 麻烦死了呢. 在腾讯花1块钱备案了一个网站www.goodgoodstu ...

  5. Lavarel之环境配置 .env

    .env 文件位于项目根目录下,作为全局环境配置文件. 1. 配置参数 // 运行环境名称 APP_ENV=local // 调试模式,开发阶段启用,上线状态禁用. APP_DEBUG=true // ...

  6. MySQL视图 definer & invoker 权限

    1.创建视图 CREATE VIEW `NewView`AS SELECT `user`.USER_ID, `user`.USER_NAME, department.DEPT_ID, departme ...

  7. H3C DHCP地址分配方式

  8. P1026 翻硬币

    题目描述 小明正在玩一个"翻硬币"的游戏.桌上放着排成一排的若干硬币.我们用 * 表示正面,用 o 表示反面(是小写字母,不是零). 比如,可能情形是:**oo***oooo 如果 ...

  9. 【HTML/CSS】BFC

    块格式化上下文(Block formatting contexts) BFC是什么? 是Web页面中盒模型布局的CSS渲染模式.它的定位体系属于常规文档流. 至少满足条件之一: float 的值不为  ...

  10. PC端网页特效

    元素偏移量offset系列 offset翻译过来就是偏移量,我们使用offset系列相关属性可以动态的得到该元素的位置(偏移),大小等 获得元素距离带有定位父元素的位置 获得元素自身的大小(宽度高度) ...