Task中Wait()和Result造成死锁

本文介绍了在使用C#Task时,Wait()和Result可能导致的死锁现象,尤其是在嵌套任务中。通过添加ConfigureAwait(false)或改为await方式,可以有效避免死锁并保持线程效率。
摘要由CSDN通过智能技术生成

        在使用Task的时候,一不留神就会造成死锁,而且难以发现,尤其是业务繁多的情况下,一个Task嵌套另一个Task的时候,下面就演示一下,在什么情况下,会产生Wait()和Result的死锁,因此,我们就要避免这样的写法。

目录

一、Wait()死锁

二、Result死锁

一、Wait()死锁

首先执行下面这段代码,点击按钮的时候,界面直接就卡死了。

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);       //获取当前的线程ID
            A().Wait();
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }

        private async Task A()
        {
            await Task.Delay(1000);
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
            //业务代码
        }

如下图所示,而且运行显示的线程是1,也就是在执行A().Wait();时,程序就死了。

死去的原因就是A方法里面,要等待1s,它们都是主线程,所以到了 A().Wait()时,主线程会卡死这里,形成了互相等待的局面,你等我,我等你,就产生了死锁。

解决死锁的方式有2种。

1.只增加一句代码即可

增加.ConfigureAwait(false)

    private void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);       //获取当前的线程ID
            A().Wait();
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }

        private async Task A()
        {
            await Task.Delay(1000).ConfigureAwait(false);
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
            //业务代码
        }

这句的意思就是,让你重新建立一个线程,把主线程让出去,这样就不会死锁了。

此时点击按钮,就会产生一个线程4,等线程4执行完毕后,就回到了主线程上。 

2. 增加await(推荐)

      private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);       //获取当前的线程ID
            //A().Wait();                                                            //A().Wait();
            await A();
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }

        private async Task A()
        {
            await Task.Delay(1000);
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
            //业务代码
        }

此时点击按钮,会看到都是同样的线程

虽然都解决了死锁,但是他们的原理是不一样的,第2种,始终都是1个主线程再执行,第1个开启了一个线程,干完事后,又回到了主线程上。

微软也建议我们async到底,一直传染下去。 

二、Result死锁

这种死锁主要是Task中,带有返回的值。

我们改造一下即可

     private void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);       //获取当前的线程ID
            string str = A().Result;
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }

        private async Task<string> A()
        {
            await Task.Delay(1000);
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
            //业务代码
            return "123";
        }

此时点击按钮,界面卡死了

解决方式和上面的一样,同样有2种方式

1.增加.ConfigureAwait(false)

    private void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);       //获取当前的线程ID
            string str = A().Result;
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }

        private async Task<string> A()
        {
            await Task.Delay(1000).ConfigureAwait(false);
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
            //业务代码
            return "123";
        }

点击按钮后,界面就不会卡了,也是创建了一个线程,完成后,回到主线程上面 

2.增加await(推荐)

     private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);       //获取当前的线程ID
            string str =await A();
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }

        private async Task<string> A()
        {
            await Task.Delay(1000);
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
            //业务代码
            return "123";
        }

此时点击按钮,界面不卡了,会看到都是同样的线程,和上面的一模一样。

拓展

当我们基于第二部分的第2种方法,加上了.ConfigureAwait(false)

将会有什么变化呢?

代码:

    private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);       //获取当前的线程ID
            string str =await A();
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }

        private async Task<string> A()
        {
            await Task.Delay(1000).ConfigureAwait(false);
            Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
            //业务代码
            return "123";
        }

效果 

界面也不卡了,但是发现到,界面还是开启了一个线程,然后回到主线程上,虽然他们的功能都是一样的,但是这种方法肯定不如单个主线程好,因为开启一个线程,也需要耗费资源。 

所以,ConfigureAwait(false)这句代码非常的重要,界面是否卡死,就是他的原因,意思就是是否立即返回主线程干活,true是,false否。

当我们改成true,又是单个主线程执行了,此时,其实ConfigureAwait(true)是句无效的代码,因为就算你返回了,那边还有一句await,await主线程,都是在一个线程上。

来源:

Task中Wait()和Result造成死锁-CSDN博客

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

故里2130-西安找工作

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值