C#async awaitデッドロック問題のまとめ

8458 ワード

デッドロックが発生する可能性のあるプログラムタイプ
1、WPF/WinFormプログラム
2、asp.Net(asp.net mvcを除く)プログラム
 
デッドロックの発生原理
非同期メソッドで返されるTaskに対してWait()を呼び出したりResultプロパティにアクセスしたりすると、デッドロックが発生する可能性があります.
次のWPFコードでデッドロックが発生します.
        private void Button_Click_7(object sender, RoutedEventArgs e)
        {
            Method1().Wait();
        }

        private async Task Method1()
        {
            await Task.Delay(100);

            txtLog.AppendText("    ");
        }

次のasp.Netmvcコードにもデッドロックが発生します.
        public ActionResult Index()
        {
            string s=Method1().Result;

            return View();
        }

        private async Task<string> Method1()
        {
            await Task.Delay(100);

            return "hello";
        }

WPFコードの例として、イベントプロセッサはMethod 1を呼び出し、Taskオブジェクトを取得し、TaskのWaitメソッドを呼び出し、Taskオブジェクトが「完了」するまで、自分の存在するスレッド、すなわちメインスレッドをブロックする.戻ってきたTaskオブジェクトを「完了」するには、メインスレッド上でawait後のコードを実行する必要があります.メインスレッドはすでにブロックされており、Taskオブジェクトの完了を待っています.するとデッドロックが発生します.
asp.Netmvcコードは同じ理屈です.
 
デッドロックを回避する方法
次のコードは問題ありません.
        private void Button_Click_8(object sender, RoutedEventArgs e)
        {
            HttpClient httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri("https://www.baidu.com/");

            string html = httpClient.GetStringAsync("/").Result;

            txtLog.AppendText(html);
        }

次のコードにも問題はありません.
        private void Button_Click_8(object sender, RoutedEventArgs e)
        {
            string html = GetHtml();

            txtLog.AppendText(html);
        }

        private string GetHtml()
        {
            HttpClient httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri("https://www.baidu.com/");

            return httpClient.GetStringAsync("/").Result;
        }

下にはデッドロックが発生します.
        private void Button_Click_8(object sender, RoutedEventArgs e)
        {
            string html = GetHtml().Result;

            txtLog.AppendText(html);
        }

        private async Task<string> GetHtml()
        {
            HttpClient httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri("https://www.baidu.com/");

            return await httpClient.GetStringAsync("/");
        }

なぜHttpClientのGetStringAsync()から返されたTaskでResutにアクセスしてデッドロックは発生せず、自分が書いたコードでデッドロックが発生したのですか?
monoのHttpClientソースコードから、いくつかのヒントを見つけることができます.
すべてのawait式の後に、ConfigureAwait(false)が追加されています.
return await resp.Content.ReadAsStringAsync ().ConfigureAwait (false);
Taskのmsdnドキュメントから分かるように、ConfigureAwait(false)はawait以降のコードが元のcontext(スレッドと理解できる)上で実行されないことを示す.
これにより,最初のWPFコードを例にとると,メインスレッドがブロックされ,Taskオブジェクトの「完了」を待つ.Method 1が100 ms呼び出された後、別のスレッドでawaitの後のコードが実行され、Taskオブジェクトが完了する.メインスレッドは実行を再開します.
HttpClientを使用してデッドロックを起こすコードを以下の形式に変更します.
        private void Button_Click_8(object sender, RoutedEventArgs e)
        {
            string html = GetHtml().Result;

            txtLog.AppendText(html);
        }

        private async Task<string> GetHtml()
        {
            HttpClient httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri("https://www.baidu.com/");

            return await httpClient.GetStringAsync("/").ConfigureAwait(false);
        }

デッドロックは現れないことがわかります
 
について述べる
  • 非同期ツールメソッドでは、可能な限りConfigureAwait(false)を加えることで、メソッドの利用者が非同期メソッドから戻ってきたTask上でWait()を呼び出したりResult属性にアクセスしたりしてデッドロック
  • を引き起こすことを防止することができる.
  • いくつかのシーンでは、awaitの後のコードを元のcontextに戻す必要があります.たとえば、awaitの後にUIコントロールにアクセスする必要があります.したがって、ConfigureAwait(false)は
  • を乱用することはできない.