隨意使用非同步的await和Result,被弄得欲仙欲死,然後看了 "Don't Block on Async Code" ,稍許明白,翻譯然後加上自己的理解以加深印象。 會死鎖的兩個例子 UI例子 public static async Task GetJsonAsync(Uri uri) { us ...
隨意使用非同步的await和Result,被弄得欲仙欲死,然後看了 Don't Block on Async Code,稍許明白,翻譯然後加上自己的理解以加深印象。
會死鎖的兩個例子
UI例子
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例子**
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();
}
}
死鎖的原因
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
根據第一點經驗:
var jsonString = await client.GetStringAsync(uri);
改成
var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false);
根據第二點經驗,調用非同步方法的代碼如下:
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();
}
}
await 是一個非同步等待
.Result是一個同步等待
同步等待在控制台程式、單元測試中不會死鎖