莫阻塞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; }
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 请求上下文。
怎样避免死锁
- 在异步方法的代码库中,尽可能使用 ConfigureAwait(false)。
- 不要阻塞任务; 从头到尾使用异步。
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); } }
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代码的主要内容,如果未能解决你的问题,请参考以下文章
同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式
DispatchQueue.main.async 阻塞主线程