小心C# 5.0 中的await and async模式造成的死锁

平时在使用C# 5.0中的await and async关键字的时候总是没注意,直到今天在调试一个ASP.NET项目时,发现在调用一个声明为async的方法后,程序老是莫名其妙的被卡住,就算声明为async的方法中的Task任务执行完毕后,外部方法的await调用还是阻塞着,后来查到了下面这篇文章,才恍然大悟原来await and async模式使用不当很容易造成程序死锁,下面这篇文章通过一个Winform示例和一个Asp.net示例介绍了await and async模式是如何造成程序死锁的,以及如何避免这种死锁。

原文链接

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.

这里我补充一下,如果你开发的是Winform程序,那么最好用第二种方法避免死锁,也就是不要阻塞主线程(也就是本文中提到的context thread),这样当await等待的Task对象线程执行完毕后,由于主线程没有被阻塞,因此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.

最后再补充说一点,本文提到的await and async死锁问题,在.Net控制台程序中并不存在。因为经过实验发现在.Net控制台程序中,await后面的代码默认就是在一个新的线程上执行的,也就是说在控制台程序中就算不调用Task.ConfigureAwait(false),await后面的代码也会在一个新启动的线程上执行,不会和主线程发生死锁。但是在Winform和Asp.net中就会发生死锁。

时间: 2024-10-09 17:40:16

小心C# 5.0 中的await and async模式造成的死锁的相关文章

Apache Spark 2.2.0 中文文档 - 集群模式概述 | ApacheCN

集群模式概述 该文档给出了 Spark 如何在集群上运行.使之更容易来理解所涉及到的组件的简短概述.通过阅读 应用提交指南 来学习关于在集群上启动应用. 组件 Spark 应用在集群上作为独立的进程组来运行,在您的 main 程序中通过 SparkContext 来协调(称之为 driver 程序). 具体的说,为了运行在集群上,SparkContext 可以连接至几种类型的 Cluster Manager(既可以用 Spark 自己的 Standlone Cluster Manager,或者

VS2015 C#6.0 中的那些新特性

? VS2015 C#6.0 中的那些新特性 前言 ? ? ? VS2015在自己机器上确实是装好了,费了老劲了,想来体验一下跨平台的快感,结果被微软狠狠的来了一棒子了,装好了还是没什么用,应该还需要装Xarmain插件,配置一些参数吧,由于这块之前从未接触过,想了想还是先不把时间继续浪费在这里了,于是乎来体验一下新特性了. 本人个人博客原文链接地址为http://aehyok.com/Blog/Detail/66.html. ? ?本文参考http://roslyn.codeplex.com,

C# 8.0中的新功能

微信公众号:Fintech极客 作者为软件开发工程师,就职于金融信息科技类公司,通过CFA一级,分享计算机和金融相结合领域的技术和知识. C# 8.0中的新功能 C# 8.0已经推出来好长一段时间了, 由于公司目前主要使用的还是6.0版本,加上之前个人事情较多,一直没有总结,今天主要查看和测试微软官方文档中的内容:https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8 只读成员(Readonly members) 在st

Spark-1.6.0中的Sort Based Shuffle源码解读

从Spark-1.2.0开始,Spark的Shuffle由Hash Based Shuffle升级成了Sort Based Shuffle.即Spark.shuffle.manager从Hash换成了Sort.不同形式是Shuffle逻辑主要是ShuffleManager的实现类不同. 在org.apache.spark.SparkEnv类中: // Let the user specify short names for shuffle managers val shortShuffleMgr

[译] C# 5.0 中的 Async 和 Await (整理中...)

C# 5.0 中的 Async 和 Await [博主]反骨仔 [本文]http://www.cnblogs.com/liqingwen/p/6069062.html 伴随着 .NET 4.5 和 Visual Studio 2012 的 C# 5.0 ,我们可以使用的新的异步模式,这里涉及到 async 和 await 关键字.有许多不同点的观点,比起之前我们所看到的代码,它的可读性和实用性是否更加突出.我们将通过一个例子,看看它与当前的做法有何“与众不同”. 线性代码与非线性代码 大部分的软

C# 5.0中引入了async 和 await

C# 5.0中引入了async 和 await.这两个关键字可以让你更方便的写出异步代码. 看个例子: [csharp] view plaincopy public class MyClass { public MyClass() { DisplayValue(); //这里不会阻塞 System.Diagnostics.Debug.WriteLine("MyClass() End."); } public Task<double> GetValueAsync(double

[C#] .NET4.0中使用4.5中的 async/await 功能实现异步

在.NET Framework 4.5中添加了新的异步操作库,但是在.NET Framework 4.0中却无法使用.这时不免面临着抉择,到底是升级整个解决方案还是不使用呢? 如果你的软件还没发布出去,建议直接使用.NET Framework 4.5吧:但是如果已经发布了,又不想用户重新升级框架到.NET Framework 4.5,那也有一个办法,那就是使用库:Microsoft.Bcl.Async 在4.5中使用async/await 的地方如下: 好处呢,我不多说,我想说的是: What

C# 6.0:在catch和finally中使用await

Asyn方法是一个现在很常用的方法,当使用async和await时,你或许曾有这样的经历,就是你想要在catch块或finally块中使用它们,比如当出现一个exception而你希望将日志记在文件或者调用一个服务将exception信息发送给server,而这些操作可能很耗时.这种情况下,在catch块中的异步方法中使用await将会很有帮助.而这已经被添加到了C# 6.0 中. 在finally块中也一样,我们也可以使用await来调用async 方法. 下面的代码块显示了怎么用它们. pu

在ASP.NET Core 2.0中使用CookieAuthentication

在ASP.NET Core中关于Security有两个容易混淆的概念一个是Authentication(认证),一个是Authorization(授权).而前者是确定用户是谁的过程,后者是围绕着他们允许做什么,今天的主题就是关于在ASP.NET Core 2.0中如何使用CookieAuthentication认证. 在ASP.NET Core 2.0中使用CookieAuthentication跟在1.0中有些不同,需要在ConfigureServices和Configure中分别设置,前者我