莫阻塞async代码

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了莫阻塞async代码相关的知识,希望对你有一定的参考价值。

最近在深入研究异步模式和async, await关键字的时候看到了Stephen Cleary的这篇文章感觉又提高了一下对这两个keyword的了解,原文链接如下

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

 

UI Example

考虑下面示例单击按钮启动其他呼叫显示文本框 (此示例 Windows 窗体同样原则适用任何 UI 应用程序) 结果

// 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;
}
"GetJsonAsync"方法进行实际的 REST 调用并解析 JSON按钮单击方法等待帮助器方法结束然后显示结果代码死锁

 

ASP.NET Example

和上面非常相似的例子,执行 REST 调用方法 ASP.NET 上下文中使用 (示例是Web API 适用任何 ASP.NET 应用程序) 

// 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();
  }
}

同样原因,这段代码也会造成死锁。

 

为什么会死锁?

 

第一个要点:一个等待另一个任务完成的方法要继续执行时是在一个上下文中执行。第一种情况下情况下一个 UI 上下文 适用任何 UI 除了控制台应用程序)。第二种情况下 ASP.NET 请求上下文

一个要点  ASP.NET 请求上下文依赖特定线程 用户界面是),允许一个线程进入。

 

下面是会发生的事情,我们从顶层方法 (Button1_Click UI / MyController.Get  ASP.NET)开始说:

  • 顶层方法调用 GetJsonAsync (在UI/ASP.NET 上下文内)。
  • GetJsonAsync 调用 HttpClient.GetStringAsync来进行REST请求 
  • GetStringAsync 返回未完成的Task,表示请求完成
  • GetJsonAsync 在GetStringAsync 返回的Task上进行await。这时上下文状态被捕获,来稍后继续运行 GetJsonAsync 方法GetJsonAsync 返回未完成的Task表明 GetJsonAsync 没有结束
  • 顶层方法阻塞 GetJsonAsync 返回的任务。也就是阻塞了上下文线程
  • …过了一会,REST请求完成GetStringAsync任务完成
  •  GetJsonAsync 现在可以继续运行了它等待它的上下文
  • 死锁顶级方法阻止上下文线程等待 GetJsonAsync 完成 GetJsonAsync 等待上下文被释放

在UI 示例中"上下文"用户界面上下文; 在ASP.NET 示例中"上下文" ASP.NET 请求上下文

 

怎样避免死锁

两个避免情况最佳做法。
 
  1. 异步方法的代码库尽可能使用 ConfigureAwait(false)
  2. 不要阻塞任务; 从头到尾使用异步
考虑第一种。库方法应该这样写:
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);
  }
}
更改 GetJsonAsync 的继续执行的方法行为,它将在线程线程上恢复。从而继续完成任务而不必等待上下文返回

 

第二种方法。"顶层"方法应该这样写:
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();
  }
}
 
更改顶层方法阻塞行为上下文从来没有真正阻止;所有"等待""异步等待"。
注意  这两种方法任何一个都可以避免死锁,但应该同时使用来获得最快的响应速度和系统性能

  

 

 

以上是关于莫阻塞async代码的主要内容,如果未能解决你的问题,请参考以下文章

Async/Await 仍然阻塞 UI?

同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式

DispatchQueue.main.async 阻塞主线程

NetTcpBinding 和 async/await WCF 阻塞

async/await 会阻塞事件循环吗? [复制]

Async/Await替代Promise的理由