1. 程式人生 > >C# async await 死鎖問題總結

C# async await 死鎖問題總結

可能發生死鎖的程式型別

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.net mvc程式碼也會出現死鎖:

        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程式碼是同樣的道理。

 

如何避免死鎖

可以試驗一下,下面的程式碼是不會有問題的:

        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),可以防止方法的使用者在非同步方法返回的Task上呼叫Wait()或訪問Result屬性而造成死鎖
  2. 在一些場景中,我們是需要讓await之後的程式碼返回原先的context執行的。如,在await之後,需要訪問UI控制元件。所以,ConfigureAwait(false)不能濫用