這篇博文主要是講解在async/await中使用阻塞式代碼導致死鎖的問題,以及如何避免出現這種死鎖。內容主要是從作者Stephen Cleary的兩篇博文中翻譯過來. 原文1:Don'tBlock on Async Code 原文2:why the AspNetSynchronizationCont ...
這篇博文主要是講解在async/await中使用阻塞式代碼導致死鎖的問題,以及如何避免出現這種死鎖。內容主要是從作者Stephen Cleary的兩篇博文中翻譯過來.
原文2:why the AspNetSynchronizationContext was removed
示例代碼:async_await中使用阻塞式代碼導致死鎖.rar
一、async/await 非同步代碼運行流程
async/await是在.NET4.5版本引入的關鍵字,讓開發者可以更加輕鬆的創建非同步方法。
我們從下圖來認識async/await的運行流程:
二、在非同步代碼中阻塞,導致死鎖的示例
UI 示例
單擊一個按鈕,將發起一個REST遠程請求並且將結果顯示到textbox控制項上。(這是一個Windows Forms程式,同樣也適用於其他任何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返回。Button1_Click方法調用Task .Result阻塞等待GetJsonAsync處理完畢並顯示結果
這段代碼會死鎖。
ASP.NET 示例
在類庫方法GetJsonAsync中發起一個REST遠程請求,這次這個GetJsonAsync在ASP.NET context中被調用。(示例是Web API項目,同樣適用於任何一個ASP.NET應用程式 - 註:非ASP.NET Core應用)
// 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示例是同一個原因。
三、是什麼原因導致的死鎖呢?
await一個Task後,在恢復繼續執行時,會試圖進入await之前的context。
第一個示例中,這個context是UI context(任何UI應用,除了控制台應用)。在第二個示例中,這個context是ASP.NET request context。
另一個需要註意的點:ASP.NET request context 沒有綁定到特定的線程上(像UI context一樣),但是request context同一時刻只允許被綁定到一個線程上。我曾經在MSDN上有發表過《關於SynchronzationContext》的文章.
死鎖是怎麼發生的呢?我們從top-level方法開始(UI的Button1_Click方法或ASP.NET的MyContoller.Get方法)
1. top-level方法調用GetJsonAsync(在UI/ASP.NET context中)。
2. GetJsonAsync通過HttpClient.GetStringAsync發起REST遠程請求(在UI/ASP.NET context中)。
3. GetStringAsync返回一個未完成Task,標識REST遠程請求還未處理完
4. GetJsonAsync方法中await GetStringAsync返回的未完成Task。等Task執行完畢,
會重新捕獲等待之前的context並使用它繼續執行GetJsonAsync。
5. GetJsonAsync中await後,攜帶context的線程會跳出GetJsonAsync方法,繼續執行後面的代碼。併在jsonTask.Result發生阻塞。此時攜帶context的線程被阻塞了。
6. 最終,REST