http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

This is a problem that is brought up repeatedly on the forums and Stack Overflow. I think it’s the most-asked question by async newcomers once they’ve learned the basics.

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 does only 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):

  1. The top-level method calls GetJsonAsync (within the UI/ASP.NET context).
  2. GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context).
  3. GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.
  4. GetJsonAsync awaits the Task returned by GetStringAsync. The context 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.
  5. The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.
  6. … Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.
  7. 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.
  8. 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”.

Preventing the Deadlock

There are two best practices (both covered in my intro post) that avoid this situation:

  1. In your “library” async methods, use ConfigureAwait(false) wherever possible.
  2. Don’t block on Tasks; use async all the way down.

Consider the first best practice.   //UI的修改

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.     //ASP.NET的修改

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.

Resources

This kind of deadlock is always the result of mixing synchronous with asynchronous code.

Usually this is because people are just trying out async with one small piece of code and use synchronous code everywhere else.

Unfortunately, partially-asynchronous code is much more complex and tricky than just making everything asynchronous.

If you do need to maintain a partially-asynchronous code base, then be sure to check out two more of Stephen Toub’s blog posts:Asynchronous Wrappers for Synchronous Methods and Synchronous Wrappers for Asynchronous Methods, as well as my AsyncEx library.

Answered Questions

There are scores of answered questions out there that are all caused by the same deadlock problem. It has shown up on WinRT, WPF, Windows Forms, Windows Phone, MonoDroid, Monogame, and ASP.NET.

Update (2014-12-01): For more details, see my MSDN article on asynchronous best practices or Section 1.2 in myConcurrency Cookbook.

还有一个姊妹篇http://blog.stephencleary.com/2012/12/dont-block-in-asynchronous-code.html

最新文章

  1. java基础知识(四)java内存机制
  2. Javascript:谈谈JS的全局变量跟局部变量
  3. SQL 性能调优日常积累
  4. plsql programming 04 条件和顺序控制
  5. ubuntu 下 数学库编译链接时找不到各种数学问题解决方法 can not fon atan 等等
  6. SQL基础检测
  7. SpringMVC Ajax返回的请求json 的方式来解决在中国字符串乱码问题
  8. aria2 加速百度网盘下载
  9. Web前端-CSS必备知识点
  10. travis-ci 中运行 puppeteer
  11. 金蝶核算项目余额表卡号余额与天财商龙CRM卡号余额对比
  12. markdown的css样式(自己写的)
  13. Nginx SSL TLS部署最佳实践
  14. dedecms后台左侧菜单500错误怎么处理
  15. Centos7搭建Postfix发送邮件 Connection timed out
  16. iBeacon室内定位原理解析【转】
  17. vue 双向数据绑定 Vue事件介绍 以及Vue中的ref获取dom节点
  18. kbmMW均衡负载与容灾(2)(转载红鱼儿)
  19. Android - View的绘制你知道多少?
  20. 【Alpha】第五次Scrum meeting

热门文章

  1. informix 通过ADO或ODBC连接提取数据时出现中文乱码的解决方法
  2. mysql 导入数据库时,报错1840的解决方法
  3. 利用VMware14安装虚拟机(Win7&amp;CentOS6.4)
  4. Java:Java 队列的遍历
  5. 【sqli-labs】 less15 POST - Blind- Boolian/time Based - Single quotes (基于bool/时间的单引号POST型盲注)
  6. 【sqli-labs】 less12 POST - Error Based - Double quotes- String-with twist (基于错误的双引号POST型字符型变形的注入)
  7. 新浪某个tab 页模仿
  8. 使用.Net Core RT 标准动态库
  9. 洛谷P1996 约瑟夫问题【队列】
  10. mysql字符集和排序规则