浅谈C# async await 死锁问题总结

时间:2021-05-19

可能发生死锁的程序类型

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:///"); 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控件的代码,否则无法规避死锁问题。

总结

  • 死锁会发生在不遵循异步编程规范——在异步方法返回的Task对象上执行Wait()或.Result时
  • ConfigureAwait(false)指定await后的代码不返回原先的context,可以避免死锁
  • 如果await之后的代码不需要返回原先的context执行,例如,仅仅是执行Http请求,获取和处理数据,那么完全可以加上ConfigureAwait(false)。
  • 如果作为类库的创作者,编写异步方法时,应尽可能的使用ConfigureAwait(false),以保证一旦类库的使用者阻塞异步方法时,不会产生死锁。
  • 在异步类库/工具方法中,应避免加入访问UI控件的代码

附加 async/await学习资料

C# Under the Hood: async/await 作者从动手写一个“可等待”的方法开始,进而通过反编译工具分析异步方法生成的的实质代码,揭示了async/await的本质——回调

What happens in an async method msdn编程指南,图示异步方法的执行流程

到此这篇关于浅谈C# async await 死锁问题总结的文章就介绍到这了,更多相关C# async await 死锁内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章