C# async await 出现死锁的原因?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
可能发生死锁的程序类型
1、WPF/WinForm程序
2、asp.net (不包括asp.net core)程序
死锁的产生原理
对异步方法返回的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代码是同样的道理。
什么时候必然会死锁,如何避免
从上面的两个例子中似乎可以得出结论:在WPF/WinForm/asp.net程序中,在异步方法上调用.Result/Wait(),就会产生死锁。然而事实并非如此。
如下面的WPF代码就不会出现死锁:(从web获取数据并显示在文本框中。此代码仅为举例说明,异步事件处理器才是正道)
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); }
把获取数据的代码摘出来吧:
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; }
完全没问题,这是肯定的。
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()方法是异步方法,我们调用它,然后用async/await的方式创建了一个自己的异步方法。先不“一路异步到底(Async All the Way)”。)
运行一下,死锁出现了。
为什么在HttpClient的GetStringAsync()方法上执行.Result不会死锁,而在自己写的异步方法上执行.Result,就出现了死锁?难道HttpClient的GetStringAsync()方法内部有什么特殊的处理?
看一下mono的HttpClient源代码,可以发现:
所有await 表达式后面,都加了ConfigureAwait (false),如
return await resp.Content.ReadAsStringAsync ().ConfigureAwait (false);
而由Task的msdn文档可以知,ConfigureAwait (false)会指示await之后的代码不在原先的context (可理解为线程)上运行。
修改一下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; }
可以发现,死锁不会出现了。
分析:GetHtml()被调用后,主线程阻塞,等待Task对象“完成”;HttpClient获取数据完毕,在另外的线程上执行了await的之后的代码,于是Task对象完成。主线程恢复执行。(注意,即使“await之后没有代码”,即GetHtml()方法体中直接写return await httpClient.GetStringAsync("/"),也是需要加.ConfigureAwait(false)的)
当然,如果事件处理器是异步的,即使不加.ConfigureAwait(false),也不会有任何问题:
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()被放到单独的类中,做成类库,那么,里面如果不加.ConfigureAwait(false),则只能假设使用这个类库的人严格遵循异步编程规范了。一旦使用者在GetHtml()上执行.Result,死锁就无可避免了。
仔细看HttpClient的源代码,可以发现,它的GetStringAsync()方法也并不是“天生的”异步方法,它也是用await运算符调用了自己的其他的异步方法,并且在每次调用后都添加了.ConfigureAwait(false)。
那么,最初的WPF程序的死锁是否可以用.ConfigureAwait(false)解决呢?注意,txtLog是一个文本框,UI控件只能在UI线程访问,所以添加上.ConfigureAwait(false)后会报错:“InvalidOperationException: 调用线程无法访问此对象,因为另一个线程拥有该对象”。那么是否可以改成这样:
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("后续代码"); }); }
依然是死锁。所以,乖乖的用异步事件处理器吧:
private async void Button_Click_7(object sender, RoutedEventArgs e) { await Method1(); } private async Task Method1() { await Task.Delay(100); txtLog.AppendText("后续代码"); }
上面的代码还说明一个问题:在异步工具方法中,不要写访问UI控件的代码,否则无法规避死锁问题。
总结
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。