莫阻塞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();
  }
}

这将更改顶层方法阻塞行为,上下文从来没有真正阻止;所有的"等待"是"异步等待"。

注意  这两种方法任何一个都可以避免死锁,但应该同时使用来获得最快的响应速度和系统性能。

  

时间: 2024-08-26 09:51:18

莫阻塞async代码的相关文章

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

原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的两篇博文中翻译过来. 原文1:Don'tBlock on Async Code 原文2:why the AspNetSynchronizationContext was removed 示例代码:async_await中使用阻塞式代码导致死锁.rar 一.async/await 异步代码运行流

java线程 - 线程唤醒后并被执行时,是在上次阻塞的代码行重新往下执行,而不是从头开始执行

今天重新把昨晚的线程同步面试题做一遍时,发现实际情况运行下来时,线程一直不同步.后来经过不断测试,发现自己的一个误区. 之前一直以为,线程如果被唤醒后再次执行时,会从头开始运行这个线程,也就是重新运行Runnable中的run()方法: 而实际情况是,被唤醒并且被执行的线程是从上次阻塞的位置从下开始运行,也就是从wait()方法后开始执行. 所以判断是否进入某一线程的条件 是用while判断,而不是用If判断判断. 下面举例说明: 如果三个线程同步工作,第一个线程打印1,2,3,4,5   ,然

Java借助Runtime调用外部程序阻塞的代码

有时候在java代码中会调用一些外部程序,比如SwfTools来转换swf.ffmpeg来转换视频等.如果你的代码这样写:Runtime.getRuntime().exec(command),会发现程序一下就执行完毕,而在命令行里要执行一会,是因为java没有等待外部程序的执行完毕,此时就需要使用阻塞,来等待外部程序执行结果: InputStream stderr = process.getInputStream(); InputStreamReader isr = new InputStrea

Tornado 框架中异步与非阻塞编程代码说明

在tornad官方文档的Docs>User's guide>Asynchronous and non-Blocking I/O部分,文中提供了几段示例代码: a.同步请求代码 from tornado.httpclient import HTTPClient def synchronous_fetch(url):     http_client = HTTPClient()     response = http_client.fetch(url)     return response.bo

异步编程系列第04章 编写Async方法

p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚. 如果你觉得这件事儿没意义翻译的又差,尽情的踩吧.如果你觉得值得鼓励,感谢留下你的赞,愿爱技术的园友们在今后每一次应该猛烈突破的时候,不选择知难而退.在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力而

异步编程系列第03章 自己写异步代码

p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚. 如果你觉得这件事儿没意义翻译的又差,尽情的踩吧.如果你觉得值得鼓励,感谢留下你的赞,愿爱技术的园友们在今后每一次应该猛烈突破的时候,不选择知难而退.在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力而

【笔记】记一次.net语法await和async的异步编程实验与笔记。

1.实践代码全记录: using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace await_测试 { class Program { static void Main(string[] args) { te

不使用回调函数的ajax请求实现(async和await简化回调函数嵌套)

在常规的服务器端程序设计中, 比如说爬虫程序, 发送http请求的过程会使整个执行过程阻塞,直到http请求响应完成代码才会继续执行, 以php为例子 $url = "http://www.google.com.hk"; $result = file_get_contents($url); echo $result; 当代码执行到第二行时,程序便陷入了等待,直到请求完成,程序才会继续往下跑将抓取到的html输出.这种做法的好处是代码简洁明了,运行流程清晰, 容易维护. 缺点就是程序的运

script标签中的async和defer

在程序中代码是一行一行执行的,html标签都是由渲染引擎来执行,代码执行时从上往下一行一行执行,当执行到alert(如下图),alert会阻塞后面代码的执行,当点击完确定之后,代码继续往下执行. javascript的内容同样可在外部进行引用,如下图所示,正常情况下执行结果和上面的内容相同,但当我们给html的script标签中加入async或者defer属性时,代码的执行过程也将会随之改变. async 为异步,顾名思义就是多个人同时做多件事,这里区分sync,sync为同步,就是一个人有序的