[转]小心C# 5.0 中的await and async模式造成的死锁
原文链接
https://www.cnblogs.com/OpenCoder/p/4434574.html
内容
UI Example
Consider the example below. A button click will initiate a REST call and display the results in a text box (this sample is for Windows Forms, but the same principles apply to any UI application).

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri);
return JObject.Parse(jsonString);
}
} // My "top-level" method.
public void Button1_Click(...)
{
var jsonTask = GetJsonAsync(...);
textBox1.Text = jsonTask.Result;
}

The “GetJson” helper method takes care of making the actual REST call and parsing it as JSON. The button click handler waits for the helper method to complete and then displays its results.
This code will deadlock.
ASP.NET Example
This example is very similar; we have a library method that performs a REST call, only this time it’s used in an ASP.NET context (Web API in this case, but the same principles apply to any ASP.NET application):

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri);
return JObject.Parse(jsonString);
}
} // My "top-level" method.
public class MyController : ApiController
{
public string Get()
{
var jsonTask = GetJsonAsync(...);
return jsonTask.Result.ToString();
}
}

This code will also deadlock. For the same reason.
What Causes the Deadlock
Here’s the situation: remember from my intro post that after you await a Task, when the method continues it will continue in a context.
In the first case, this context is a UI context (which applies to any UI except Console applications). In the second case, this context is an ASP.NET request context.
One other important point: an ASP.NET request context is not tied to a specific thread (like the UI context is), but it doesonly allow one thread in at a time. This interesting aspect is not officially documented anywhere AFAIK, but it is mentioned in my MSDN article about SynchronizationContext.
So this is what happens, starting with the top-level method (Button1_Click for UI / MyController.Get for ASP.NET):
- The top-level method calls GetJsonAsync (within the UI/ASP.NET context).
- GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context,这里的within the context表示的是GetJsonAsync方法依然用的是执行top-level method的线程来执行,也就是主线程).
- GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.
- GetJsonAsync awaits the Task returned by GetStringAsync. The context(这里的context依然指的是执行top-level method的线程,当GetStringAsync方法执行完毕返回后,GetJsonAsync会继续用执行top-level method的线程来执行await关键字之后的代码,这也是造成本例中代码会死锁的原因) is captured and will be used to continue running the GetJsonAsync method later. GetJsonAsync returns an uncompleted Task, indicating that the GetJsonAsync method is not complete.
- The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.
- … Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.
- The continuation for GetJsonAsync is now ready to run, and it waits for the context to be available so it can execute in the context.
- Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync is waiting for the context to be free so it can complete.
For the UI example, the “context” is the UI context; for the ASP.NET example, the “context” is the ASP.NET request context. This type of deadlock can be caused for either “context”.
上面内容的大致意思就是说在使用await and async模式时,await关键字这一行后面的代码块会被一个context(也就是上面提到的ASP.NET request contex和UI context)线程继续执行,如果我们将本例中调用top-level method的线程称为线程A(即context线程),由于GetJsonAsync方法也是由线程A调用的,所以当GetJsonAsync方法中await的GetStringAsync方法执行完毕后,GetJsonAsync需要重新使用线程A执行await代码行之后的代码,而现在由于线程A在top-level method的代码中因为访问了jsonTask.Result被阻塞了(因为线程A调用top-level method代码中jsonTask.Result的时候,await的GetStringAsync的Task还没执行完毕,所以被线程A阻塞),所以GetJsonAsync无法重新使用线程A执行await代码行之后的代码块,也被阻塞,所以形成了死锁。也就是说top-level method代码中线程A因为等待GetJsonAsync中await的GetStringAsync结束被阻塞,而GetStringAsync也等待线程A在top-level method的阻塞结束获得线程A来执行GetJsonAsync中await代码行后面的代码也被阻塞,两个阻塞相互等待,相互死锁。
Preventing the Deadlock
There are two best practices (both covered in my intro post) that avoid this situation:
- In your “library” async methods, use ConfigureAwait(false) wherever possible.
- Don’t block on Tasks; use async all the way down.
- 如果想结束async & await模式的调用,启动一个新的线程去await异步方法的返回结果
这里我补充一下,如果你开发的是Winform程序,那么最好用第二种方法避免死锁,也就是不要阻塞主线程,这样当await等待的Task对象线程执行完毕后,由于主线程没有被阻塞,因此await后面的代码就会在恰当的时候(这里提到的“恰当的时候”是由.Net Framework自己判断的,.Net Framework会安排主线程在某个时候继续执行await后面的代码)继续在主线程上执行完毕。之所以在Winform中不推荐用第一种方法是因为第一种方法会让await后面的代码在另外的线程上执行,而不再是在主线程上执行,如果await后有代码设置了Winform控件的值,那么会引起Winform程序的线程安全问题,所以在Winform中最好的办法还是不要阻塞主线程,让await后面的代码能够在主线程上执行。但在Asp.net中用上面第一种或第二种方法都可以,不存在线程安全问题。
Consider the first best practice. The new “library” method looks like this:

public static async Task<JObject> GetJsonAsync(Uri uri)
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false);
return JObject.Parse(jsonString);
}
}

This changes the continuation behavior of GetJsonAsync so that it does not resume on the context. Instead, GetJsonAsync will resume on a thread pool thread. This enables GetJsonAsync to complete the Task it returned without having to re-enter the context.
Consider the second best practice. The new “top-level” methods look like this:

public async void Button1_Click(...)
{
var json = await GetJsonAsync(...);
textBox1.Text = json;
} public class MyController : ApiController
{
public async Task<string> Get()
{
var json = await GetJsonAsync(...);
return json.ToString();
}
}

This changes the blocking behavior of the top-level methods so that the context is never actually blocked; all “waits” are “asynchronous waits”.
Note: It is best to apply both best practices. Either one will prevent the deadlock, but both must be applied to achieve maximum performance and responsiveness.
The third best practice:如果想结束async & await模式的调用,启动一个新的线程去await异步方法的返回结果

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri);
return JObject.Parse(jsonString);
}
} // My "top-level" method.
public string Get()
{
string jsonResultString = string.Empty; Task.Run(async () =>
{
jsonResultString = await GetJsonAsync(...);
}).Wait();//此处启动线程是为了防止Async & Await模式造成死锁 return jsonResultString;
}

这样因为GetJsonAsync方法是由Task.Run新启动的线程来调用的,所以在await GetJsonAsync(...)执行完毕之后,.Net Framework就会用Task.Run新启动的线程来执行await之后的代码,不会和top-level method的线程(即context线程)相互阻塞,造成死锁。
最后再补充说一点,本文提到的await and async死锁问题,在.Net控制台程序中并不存在。因为经过实验发现在.Net控制台程序中,await关键字这一行后面的代码默认就是在一个新的线程上执行的,也就是说在控制台程序中就算不调用Task.ConfigureAwait(false),await关键字这一行后面的代码也会在一个新启动的线程上执行,不会和主线程发生死锁。但是在Winform和Asp.net中就会发生死锁。
[转]小心C# 5.0 中的await and async模式造成的死锁的更多相关文章
- 小心C# 5.0 中的await and async模式造成的死锁
平时在使用C# 5.0中的await and async关键字的时候总是没注意,直到今天在调试一个ASP.NET项目时,发现在调用一个声明为async的方法后,程序老是莫名其妙的被卡住,就算声明为as ...
- 聊一聊C# 8.0中的await foreach
AsyncStreamsInCShaper8.0 很开心今天能与大家一起聊聊C# 8.0中的新特性-Async Streams,一般人通常看到这个词表情是这样. 简单说,其实就是C# 8.0中支持aw ...
- C#8.0中的 await foreach
AsyncStreamsInCShaper 8.0 C# 8.0中支持异步返回枚举类型async Task<IEnumerable<T>> sync Streams这个功能已经 ...
- Apache Spark 2.2.0 中文文档 - 集群模式概述 | ApacheCN
集群模式概述 该文档给出了 Spark 如何在集群上运行.使之更容易来理解所涉及到的组件的简短概述.通过阅读 应用提交指南 来学习关于在集群上启动应用. 组件 Spark 应用在集群上作为独立的进程组 ...
- VS2015 C#6.0 中的那些新特性(转载)
自动属性初始化 (Initializers for auto-properties) 以前我们是这么写的 为一个默认值加一个后台字段是不是很不爽,现在我们可以这样写 只读属性的初始化(Getter-o ...
- C#6.0 中的那些新特性
C#6.0 中的那些新特性 前言 VS2015在自己机器上确实是装好了,费了老劲了,想来体验一下跨平台的快感,结果被微软狠狠的来了一棒子了,装好了还是没什么用,应该还需要装Xarmain插件,配置一些 ...
- Spark-1.6.0中的Sort Based Shuffle源码解读
从Spark-1.2.0开始,Spark的Shuffle由Hash Based Shuffle升级成了Sort Based Shuffle.即Spark.shuffle.manager从Hash换成了 ...
- [译] C# 5.0 中的 Async 和 Await (整理中...)
C# 5.0 中的 Async 和 Await [博主]反骨仔 [本文]http://www.cnblogs.com/liqingwen/p/6069062.html 伴随着 .NET 4.5 和 V ...
- [C#] .NET4.0中使用4.5中的 async/await 功能实现异
好东西需要分享 原文出自:http://www.itnose.net/detail/6091186.html 在.NET Framework 4.5中添加了新的异步操作库,但是在.NET Framew ...
随机推荐
- Linux内核中进程上下文、中断上下文、原子上下文、用户上下文的理解【转】
转自:http://blog.csdn.net/laoliu_lcl/article/details/39972459 进程上下文和中断上下文是操作系统中很重要的两个概念,这两个概念在操作系统课程中不 ...
- crontab每10秒钟执行一次
1.使用sleep 在crontab中加入 * * * * * sleep 10; /bin/date >>/tmp/date.txt* * * * * sleep 20; /bin/da ...
- js中的事件委托或是事件代理详解(转载)
起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...
- CentOS 安装codeblocks
1. 安装wxWidgets .tar.bz2 [root@luozhonghua codeblocks]# cd wxWidgets- [root@luozhonghua wxWidgets-]# ...
- mysql 某字段插入随机数
UPDATE `表名` SET `字段名`=ceiling(rand()*500000+500000) WHERE (条件); 写入11位手机 UPDATE xm_user a SET a.user_ ...
- PHP并发操作下的加锁
最近做后台统计的工具,统计肯定是一个需要运算好久的数据 容许一个用户在一个时间内进行操作,这个时候就需要用到锁了,将这个操作过程锁起来.在用了cache的时候,cache失效可能导致瞬间的多数并发请求 ...
- 【Atcoder】AGC027 题解
A - Candy Distribution Again 大意:有x个糖给n个小朋友,必须分完,小朋友得到糖数为一个确切值的时候小朋友会开心,求最多的开心数 题解 直接排序然后贪心分,如果分到最后一个 ...
- BeautifulSoup使用总结
一.介绍 BeautifulSoup为一个python库,它可以接收一个HTML或XML的字符串或文件,并返回一个BeautifulSoup对象,之后我们可以使用BeautifulSoup提供的众多方 ...
- django表单字段
在通过django.forms.Forms创建表单字段时,其中每个字段都有一些自定义的验证逻辑和一些其他钩子 虽然表单字段的Field类主要使用在Form类中,但也可以直接实例化来使用它们,以更好的了 ...
- [转]LCT讲解
LCT (1)维护一个序列,支持下列操作: 区间求和 区间求最值 区间修改 求连续子段和 这个线段树就可以解决 具体做法不加累述了 (2)维护一个序列,支持下列操作: 区间求和 区间求最值 区间修改 ...