可能发生死锁的程序类型
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);
- }
可以发现, 死锁不会出现了
后话
在异步工具方法中, 尽可能的加上 ConfigureAwait(false), 可以防止方法的使用者在异步方法返回的 Task 上调用 Wait() 或访问 Result 属性而造成死锁
在一些场景中, 我们是需要让 await 之后的代码返回原先的 context 执行的. 如, 在 await 之后, 需要访问 UI 控件. 所以, ConfigureAwait(false) 不能滥用
来源: https://www.cnblogs.com/sdBob/p/12151013.html