前一篇有講到async的概念
這篇來看看使用async要注意的一件事情,就是可能會造成deadlock的情況
這邊要先有一個小觀念,就是context
context中文翻譯叫做前後文,在這邊就是指"當下的執行環境和狀態"
在async和await的機制下,遇到await的時候,會有一個專門的物件來紀錄context,這個物件叫做SynchronizationContext
為什麼需要這個物件呢,主要的目的是為了簡化用切換執行緒的操作
比如說有個操作,從UI點個按鍵 >> 拿資料 >>更新UI
其中點按鍵和更新UI都是在UI的thread完成,拿資料是在另一個thread,所以中間會需要做thread的切換
如果是使用await的話,系統就會幫忙handle這些事情,依照內部的演算法來看是要切換執行緒去執行
或是說是使用SynchronizationContext.current去紀錄當前的context,以便task完成後可以在這個context底下繼續下去
而造成deadlock的原因,就是因為這個SynchronizationContext,以下是個範例:
這個是從 https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html 貼來的
// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
// (real-world code shouldn't use HttpClient in a using block; this is just example code)
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();
}
}
這邊可以先注意到一件事情,就是Get()這個方法並不是async方法
所以在執行到GetJsonAsync(...);
這一行的時候,會把這個thread block在這邊等待這個task完成
同時也把SynchronizationContext也鎖在這邊,因為他在等待GetJsonAsync(...)
裡面回傳的那個Task完成,才能繼續接下來的動作
而在進到GetJsonAsync(...);
裡面呢,因為裡面有個await,在client.GetStringAsync(uri);
完成之後,會去找SynchronizationContext來完成接下來的動作
只是這時候最外層的thread已經SynchronizationContext給block住了(因為thread在等待task完成),所以就造成兩邊都在等的情況
Task在等SynchronizationContext,thread佔住SynchronizationContext在等Task
這篇裡面有比較詳細的順序說明:
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
https://www.huanlintalk.com/2016/01/asyc-deadlock-in-aspbet.html
不過總歸一句就是,因為有thread把它block住了,才會造成這種情況
所以第一個解決方式就是把所有方法都改成async方法
第二種呢就是在async方法的await後面加上ConfigureAwait(false)
這樣當await方法執行完之後,如果拿不到SynchronizationContext的話,他會另外用一個thread去完成剩下的工作
話是這麼說,其實我對這些觀念也不是特別清楚,似懂非懂這樣
不過個人是覺得寫多就會慢慢掌握訣竅了
就... 好好努力吧XD