淺談C# async await 死鎖問(wèn)題總結(jié)
可能發(fā)生死鎖的程序類型
1、WPF/WinForm程序
2、asp.net (不包括asp.net core)程序
死鎖的產(chǎn)生原理
對(duì)異步方法返回的Task調(diào)用Wait()或訪問(wèn)Result屬性時(shí),可能會(huì)產(chǎn)生死鎖。
下面的WPF代碼會(huì)出現(xiàn)死鎖:
private void Button_Click_7(object sender, RoutedEventArgs e) { Method1().Wait(); } private async Task Method1() { await Task.Delay(100); txtLog.AppendText("后續(xù)代碼"); }
下面的asp.net mvc代碼也會(huì)出現(xiàn)死鎖:
public ActionResult Index() { string s=Method1().Result; return View(); } private async Task<string> Method1() { await Task.Delay(100); return "hello"; }
以WPF代碼為例,事件處理器調(diào)用Method1,得到Task對(duì)象,然后調(diào)用Task的Wait方法,阻塞自己所在的線程,即主線程,直到Task對(duì)象“完成”。而返回的Task對(duì)象要想“完成”,必須在主線程上執(zhí)行await之后的代碼。而主線程早就處于阻塞狀態(tài),它在等待Task對(duì)象完成!于是死鎖就產(chǎn)生了。
asp.net mvc代碼是同樣的道理。
什么時(shí)候必然會(huì)死鎖,如何避免
從上面的兩個(gè)例子中似乎可以得出結(jié)論:在WPF/WinForm/asp.net程序中,在異步方法上調(diào)用.Result/Wait(),就會(huì)產(chǎn)生死鎖。然而事實(shí)并非如此。
如下面的WPF代碼就不會(huì)出現(xiàn)死鎖:(從web獲取數(shù)據(jù)并顯示在文本框中。此代碼僅為舉例說(shuō)明,異步事件處理器才是正道)
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; html = "【" + html + "】"; txtLog.AppendText(html); }
把獲取數(shù)據(jù)的代碼摘出來(lái)吧:
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/"); string html=httpClient.GetStringAsync("/").Result; html = "【" + html + "】"; return html; }
完全沒(méi)問(wèn)題,這是肯定的。
GetHtml()可以寫成異步方法,再改一下:
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/"); string html=await httpClient.GetStringAsync("/"); html = "【" + html + "】"; return html; }
(HttpClient的GetStringAsync()方法是異步方法,我們調(diào)用它,然后用async/await的方式創(chuàng)建了一個(gè)自己的異步方法。先不“一路異步到底(Async All the Way)”。)
運(yùn)行一下,死鎖出現(xiàn)了。
為什么在HttpClient的GetStringAsync()方法上執(zhí)行.Result不會(huì)死鎖,而在自己寫的異步方法上執(zhí)行.Result,就出現(xiàn)了死鎖?難道HttpClient的GetStringAsync()方法內(nèi)部有什么特殊的處理?
看一下mono的HttpClient源代碼,可以發(fā)現(xiàn):
所有await 表達(dá)式后面,都加了ConfigureAwait (false),如
return await resp.Content.ReadAsStringAsync ().ConfigureAwait (false);
而由Task的msdn文檔可以知,ConfigureAwait (false)會(huì)指示await之后的代碼不在原先的context (可理解為線程)上運(yùn)行。
修改一下GetHtml()異步方法的代碼:
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/"); string html=await httpClient.GetStringAsync("/").ConfigureAwait(false); html = "【" + html + "】"; return html; }
可以發(fā)現(xiàn),死鎖不會(huì)出現(xiàn)了。
分析:GetHtml()被調(diào)用后,主線程阻塞,等待Task對(duì)象“完成”;HttpClient獲取數(shù)據(jù)完畢,在另外的線程上執(zhí)行了await的之后的代碼,于是Task對(duì)象完成。主線程恢復(fù)執(zhí)行。(注意,即使“await之后沒(méi)有代碼”,即GetHtml()方法體中直接寫return await httpClient.GetStringAsync("/"),也是需要加.ConfigureAwait(false)的)
當(dāng)然,如果事件處理器是異步的,即使不加.ConfigureAwait(false),也不會(huì)有任何問(wèn)題:
private async void Button_Click_8(object sender, RoutedEventArgs e) { string html = await GetHtml(); txtLog.AppendText(html); } private async Task<string> GetHtml() { HttpClient httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("https://www.baidu.com/"); string html = await httpClient.GetStringAsync("/"); html = "【" + html + "】"; return html; }
試想一下,如果GetHtml()被放到單獨(dú)的類中,做成類庫(kù),那么,里面如果不加.ConfigureAwait(false),則只能假設(shè)使用這個(gè)類庫(kù)的人嚴(yán)格遵循異步編程規(guī)范了。一旦使用者在GetHtml()上執(zhí)行.Result,死鎖就無(wú)可避免了。
仔細(xì)看HttpClient的源代碼,可以發(fā)現(xiàn),它的GetStringAsync()方法也并不是“天生的”異步方法,它也是用await運(yùn)算符調(diào)用了自己的其他的異步方法,并且在每次調(diào)用后都添加了.ConfigureAwait(false)。
那么,最初的WPF程序的死鎖是否可以用.ConfigureAwait(false)解決呢?注意,txtLog是一個(gè)文本框,UI控件只能在UI線程訪問(wèn),所以添加上.ConfigureAwait(false)后會(huì)報(bào)錯(cuò):“InvalidOperationException: 調(diào)用線程無(wú)法訪問(wèn)此對(duì)象,因?yàn)榱硪粋€(gè)線程擁有該對(duì)象”。那么是否可以改成這樣:
private void Button_Click_7(object sender, RoutedEventArgs e) { Method1().Wait(); } private async Task Method1() { await Task.Delay(100).ConfigureAwait(false); Dispatcher.Invoke(() => { txtLog.AppendText("后續(xù)代碼"); }); }
依然是死鎖。所以,乖乖的用異步事件處理器吧:
private async void Button_Click_7(object sender, RoutedEventArgs e) { await Method1(); } private async Task Method1() { await Task.Delay(100); txtLog.AppendText("后續(xù)代碼"); }
上面的代碼還說(shuō)明一個(gè)問(wèn)題:在異步工具方法中,不要寫訪問(wèn)UI控件的代碼,否則無(wú)法規(guī)避死鎖問(wèn)題。
總結(jié)
- 死鎖會(huì)發(fā)生在不遵循異步編程規(guī)范——在異步方法返回的Task對(duì)象上執(zhí)行Wait()或.Result時(shí)
- ConfigureAwait(false)指定await后的代碼不返回原先的context,可以避免死鎖
- 如果await之后的代碼不需要返回原先的context執(zhí)行,例如,僅僅是執(zhí)行Http請(qǐng)求,獲取和處理數(shù)據(jù),那么完全可以加上ConfigureAwait(false)。
- 如果作為類庫(kù)的創(chuàng)作者,編寫異步方法時(shí),應(yīng)盡可能的使用ConfigureAwait(false),以保證一旦類庫(kù)的使用者阻塞異步方法時(shí),不會(huì)產(chǎn)生死鎖。
- 在異步類庫(kù)/工具方法中,應(yīng)避免加入訪問(wèn)UI控件的代碼
附加 async/await學(xué)習(xí)資料
C# Under the Hood: async/await 作者從動(dòng)手寫一個(gè)“可等待”的方法開始,進(jìn)而通過(guò)反編譯工具分析異步方法生成的的實(shí)質(zhì)代碼,揭示了async/await的本質(zhì)——回調(diào)
What happens in an async method msdn編程指南,圖示異步方法的執(zhí)行流程
到此這篇關(guān)于淺談C# async await 死鎖問(wèn)題總結(jié)的文章就介紹到這了,更多相關(guān)C# async await 死鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#中派生類調(diào)用基類構(gòu)造函數(shù)用法分析
這篇文章主要介紹了C#中派生類調(diào)用基類構(gòu)造函數(shù)用法,實(shí)例分析了派生類調(diào)用基類構(gòu)造函數(shù)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04C#使用OpenCvSharp4庫(kù)讀取電腦攝像頭數(shù)據(jù)并實(shí)時(shí)顯示
OpenCvSharp4庫(kù)是一個(gè)基于.Net封裝的OpenCV庫(kù),本文主要給大家介紹了C#使用OpenCvSharp4庫(kù)讀取電腦攝像頭數(shù)據(jù)并實(shí)時(shí)顯示的詳細(xì)方法,感興趣的朋友可以參考下2024-01-01探討Object轉(zhuǎn)為String的幾種簡(jiǎn)易形式詳解
本篇文章是對(duì)Object轉(zhuǎn)為String的幾種簡(jiǎn)易形式進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06c#創(chuàng)建windows服務(wù)(Windows Services)詳細(xì)步驟
這篇文章主要介紹了c#創(chuàng)建windows服務(wù)(Windows Services)詳細(xì)步驟,大家參考使用吧2013-12-12C#實(shí)現(xiàn)簡(jiǎn)單合并word文檔的方法
這篇文章主要介紹了C#實(shí)現(xiàn)簡(jiǎn)單合并word文檔的方法,涉及C#針對(duì)word文檔的讀取、插入、保存等技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-09-09