Task中Wait()和Result造成死锁

        在使用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博客

猜你喜欢

转载自blog.csdn.net/u012563853/article/details/134767005
今日推荐