在使用Task的时候,一不留神就会造成死锁,而且难以发现,尤其是业务繁多的情况下,一个Task嵌套另一个Task的时候,下面就演示一下,在什么情况下,会产生Wait()和Result的死锁,因此,我们就要避免这样的写法。
目录
一、Wait()死锁
二、Result死锁
首先执行下面这段代码,点击按钮的时候,界面直接就卡死了。
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到底,一直传染下去。
这种死锁主要是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 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 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 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 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博客