【C#】await & Result DeadLock

随意使用异步的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

上面两个例子的运行过程是:

  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空闲,然后它可以完成。

防止死锁

两点经验:

  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是一个同步等待

同步等待在控制台程序、单元测试中不会死锁

原文地址:https://www.cnblogs.com/catzhou/p/10727546.html