• 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)不能滥用

    3. 一个是在异步执行时捕获上下文,一个是在异步执行时不捕获上下文.

      1.当ConfigureAwait(true),代码由同步执行进入异步执行时,当前同步执行的线程上下文信息(比如HttpConext.Current,Thread.CurrentThread.CurrentCulture)就会被捕获并保存至SynchronizationContext中,供异步执行中使用,并且供异步执行完成之后(await之后的代码)的同步执行中使用(虽然await之后是同步执行的,但是发生了线程切换,会在另外一个线程中执行「ASP.NET场景」)。这个捕获当然是有代价的,当时我们误以为性能问题是这个地方的开销引起,但实际上这个开销很小,在我们的应用场景不至于会带来性能问题。

      2.当Configurewait(flase),则不进行线程上下文信息的捕获,async方法中与await之后的代码执行时就无法获取await之前的线程的上下文信息,在ASP.NET中最直接的影响就是HttpConext.Current的值为null。

      2|0什么时候该使用?


      综上所述,如果你的异步方法,以及异步后的方法不涉及上下文操作,即加上ConfigureAwait(false),这样可以减少性能开销.

  • 相关阅读:
    Python基础(函数)
    Python基础(列表中变量与内存关系)
    linux文件、目录管理
    安装、登入centos7
    Python基础(if语句、运算符)
    Python基础(列表、元组)
    Python基础(变量、字符编码、数据类型)
    初识Python
    解决子级用css float浮动 而父级div没高度不能自适应高度
    PHP服务端优化全面总结
  • 原文地址:https://www.cnblogs.com/cxxtreasure/p/13642397.html
Copyright © 2020-2023  润新知