【C#】await & Result DeadLock

隨意使用異步的await和Result,被弄得欲仙欲死,而後看了 Don't Block on Async Code,稍許明白,翻譯而後加上本身的理解以加深印象。html

會死鎖的兩個例子json

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例子**async

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線程

上面兩個例子的運行過程是:翻譯

  1. 在UI/ASP.NET context,調用GetJsonAsync方法;
  2. 在UI/ASP.NET context,GetJsonAsync方法調用HttpClient.GetStringAsync開始一個REST請求;
  3. GetStringAsync返回一個未完成的Task,表示REST請求沒有完成;
  4. GetJsonAsync等待GetStringAsync返回的Task。當前Context被捕獲(保存),當前Context在GetJsonAsync完成時將被調用。GetJsonAsync返回一個未完成的Task,表示GetJsonAsync方法未完成;
  5. jsonTask.Result同步阻塞GetJsonAsync返回的任務,即阻塞context;
  6. ...而後,REST請求完成了,而後通知GetStringAsync方法;
  7. GetStringAsync準備繼續任務,他等待context可用,而後他能夠在context運行;
  8. 死鎖!jsonTask.Result阻塞了context線程,等待GetStringAsync完成,GetStringAsync等待context空閒,而後它能夠完成。

防止死鎖code

兩點經驗:htm

  1. 異步方法中,儘量添加ConfigureAwait(false)
  2. 別阻塞;使用 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是一個同步等待

同步等待在控制檯程序、單元測試中不會死鎖

相關文章
相關標籤/搜索