C# async await 死鎖問題總結

可能發生死鎖的程序類型html

一、WPF/WinForm程序git

二、asp.net (不包括asp.net mvc)程序github

 

死鎖的產生原理api

對異步方法返回的Task調用Wait()或訪問Result屬性時,可能會產生死鎖。mvc

以下面的WPF代碼:app

        private void Button_Click_7(object sender, RoutedEventArgs e)
        {
            Method1().Wait();
        }

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

            txtLog.AppendText("後續代碼");
        }

或下面的asp.net mvc代碼:asp.net

        public ActionResult Index()
        {
            string s=Method1().Result;

            return View();
        }

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

            return "hello";
        }

以WPF代碼爲例,事件處理器調用Method1,獲得Task對象,而後調用Task的Wait方法,阻塞本身所在的線程,即主線程,直到Task對象「完成」。而返回的Task對象要想「完成」,必須在主線程上執行await以後的代碼。而主線程早就處理阻塞狀態,它在等待Task對象完成!因而死鎖就產生了。異步

asp.net mvc代碼是一樣的道理。async

 

如何避免死鎖工具

能夠試驗一下,下面的代碼是不會有問題的:

        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對象「完成」;Method1被調用100ms後,在另外的線程上執行了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);
        }

能夠發現,死鎖不會出現了

 

後話

  1. 在工具方法中,儘量的加上ConfigureAwait(false),能夠防止工具方法的使用者在異步方法的返回值上調用Wait()或訪問Result屬性而形成死鎖
  2. 在一些場景中,咱們是須要讓await以後的代碼返回原先的context執行的。如,在await以後,須要訪問UI控件。因此,ConfigureAwait(false)不能濫用
相關文章
相關標籤/搜索