[C#] await & Result DeadLock
随意使用异步的 await 和 Result, 被弄得欲仙欲死, 然后看了 Don't Block on Async Code, 稍许明白, 翻译然后加上自己的理解以加深印象.
会死锁的两个例子
UI 例子
- public static async Task 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 例子 **
- public static async Task 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();
- }
- }
死锁的原因
await 一个 Task 后, 当 Task 完成后将继续一个 Context.
UI 例子的 content 是 UI content,ASP.NET 例子的 Content 是 request content. 在任何时候, 这两个 content 只能属于一个线程, 是不能被具体的线程捆绑 (tied). 这个有趣或者恶心的特色没被官方文档说明, 只在 my MSDN article about SynchronizationContext.
上面两个例子的运行过程是:
在 UI/ASP.NET context, 调用 GetJsonAsync 方法; 在 UI/ASP.NET context,GetJsonAsync 方法调用 HttpClient.GetStringAsync 开始一个 REST 请求; GetStringAsync 返回一个未完成的 Task, 表示 REST 请求没有完成; GetJsonAsync 等待 GetStringAsync 返回的 Task. 当前 Context 被捕获 (保存), 当前 Context 在 GetJsonAsync 完成时将被调用. GetJsonAsync 返回一个未完成的 Task, 表示 GetJsonAsync 方法未完成; jsonTask.Result 同步阻塞 GetJsonAsync 返回的任务, 即阻塞 context; ... 然后, REST 请求完成了, 然后通知 GetStringAsync 方法; GetStringAsync 准备继续任务, 他等待 context 可用, 然后他可以在 context 运行; 死锁! jsonTask.Result 阻塞了 context 线程, 等待 GetStringAsync 完成, GetStringAsync 等待 context 空闲, 然后它可以完成.
防止死锁
两点经验:
异步方法中, 尽可能添加 ConfigureAwait(false) ; 别阻塞; 使用 async
根据第一点经验:
根据第二点经验, 调用异步方法的代码如下:
- public async void Button1_Click(...)
- {
- var JSON = await GetJsonAsync(...);
- textBox1.Text = JSON;
- }
- public class MyController : ApiController
- {
- public async Task Get()
- {
- var JSON = await GetJsonAsync(...);
- return JSON.ToString();
- }
- }
await 是一个异步等待
.Result 是一个同步等待
同步等待在控制台程序, 单元测试中不会死锁
来源: https://www.2cto.com/kf/201904/804504.html