【C#】await & Result DeadLock

2807 ワード

非同期のawaitやResultを勝手に使って、死にそうになった後、Don't Block on Async Codeを見て、少し分かり、翻訳して自分の理解を加えて印象を深めました.
デッドロックの2つの例
UI例
    public static async Task 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 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が1つ完了すると、Taskが完了するとContextが続行されます.
UI例のcontentはUI content,ASP.NET例のContentはrequest contentである.いずれにしても、この2つのcontentは1つのスレッドにしか属しず、具体的なスレッドに縛られない(tied).この面白さや吐き気の特徴は公式ドキュメントで説明されておらず、my MSDN article about SynchronizationContextのみです.
上記の2つの例の実行手順は、次のとおりです.
  • UI/ASP.NET contextでGetJsonAsyncメソッドを呼び出します.
  • UI/ASP.NET contextで、GetJsonAsyncメソッドはHttpClient.GetStringAsyncを呼び出してREST要求を開始する.
  • GetStringAsyncは、REST要求が完了していないことを示す未完了のTaskを返す.
  • GetJsonAsync GetStringAsyncが返すTaskを待つ.現在のContextはキャプチャ(保存)され、現在のContextはGetJsonAsyncの完了時に呼び出されます.GetJsonAsyncは、GetJsonAsyncメソッドが完了していないことを示す未完了のTaskを返す.
  • jsonTask.Result同期ブロックGetJsonAsyncが返すタスク、すなわちブロックcontext;
  • ...その後、REST要求が完了し、GetStringAsyncメソッドに通知される.
  • GetStringAsyncは、contextが使用可能になるのを待ってからcontextで実行できるタスクを継続する準備をしています.
  • デッドロック!jsonTask.Resultはcontextスレッドをブロックし、GetStringAsyncの完了を待機し、GetStringAsyncはcontextの空きを待機し、完了することができます.

  • デッドロック防止
    2つの経験:
  • 非同期メソッドでは、できるだけConfigureAwait(false)を追加します.
  • ブロックしないでください.async
  • の使用
    第一の経験によると、var jsonString=await client.GetStringAsync(uri);var jsonString=await client.GetStringAsync(uri)-ConfigureAwait(false)に変更します.
    第2の経験によれば、非同期メソッドを呼び出すコードは以下の通りである.
        public async void Button1_Click(...)
        {
            var json = await GetJsonAsync(...);
            textBox1.Text = json;
        }
    
        public class MyController : ApiController
        {
            public async Task Get()
            {
                var json = await GetJsonAsync(...);
                return json.ToString();
            }
        }

    awaitは非同期待機です.Resultは同期待機です.
    コンソールプログラム、ユニットテストでデッドロックしない同期待機