我使用单个控制器和单个方法创建了一个简单的WebApi项目:
public static class DoIt
{
public static async Task<string> GetStrAsync(Uri uri)
{
using (var client = new HttpClient())
{
var str = await client.GetStringAsync(uri);
return str;
}
}
}
public class TaskRunResultController : ApiController
{
public string Get()
{
var task = Task.Run(() =>
DoIt.GetStrAsync(new Uri("http://google.com"))
);
var result = task.Result;
return result;
}
}
我非常了解async / await和tasks;斯蒂芬克莱里几乎虔诚地跟随他。只是.Result
的存在让我感到焦虑,我希望这会陷入僵局。我理解Task.Run(...)
是浪费的,导致在等待异步DoIt()
完成时占用一个线程。
问题是这不是死锁,它给了我心悸。
我看到了https://stackoverflow.com/a/32607091/1801382之类的答案,而且我还发现当lambda执行时SynchronizationContext.Current
为空。但是,我也有类似的问题,询问为什么上面的代码 死锁,并且我已经看到在使用ConfigureAwait(false)
(不捕获上下文)的情况下发生死锁与.Result
。
是什么给出了?
答案 0 :(得分:5)
Result
本身不会导致死锁。死锁是指代码的两个部分都在等待彼此。 Result
只是一次等待,因此它可能是死锁的一部分,但它不一定总是导致死锁。
在standard example中,Result
等待任务完成,同时保留上下文,但任务无法完成,因为它等待上下文是免费的。所以有一个僵局 - 他们正在等待彼此。
ASP.NET Core does not have a context at all,因此Result
不会死锁。 (由于其他原因,它仍然不是一个好主意,但它不会死锁)。
问题是这不是死锁,而是让我心悸。
这是因为Task.Run
任务不需要完成上下文。所以调用Task.Run(...).Result
是安全的。 Result
正在等待任务完成,它正在保持上下文(阻止请求的任何其他部分在该上下文中执行),但这没关系,因为Task.Run
任务没有需要上下文。
Task.Run
在ASP.NET上仍然不是一个好主意(出于其他原因),但这是一种非常有用的技术,不时有用。它不会死锁,因为Task.Run
任务不需要上下文。
但是,我也有类似的问题,问为什么上面的代码会出现死锁,
相似但不完全相同。仔细查看这些问题中的每个代码语句,并问自己它运行的上下文。
我看到在与.Result一起使用ConfigureAwait(false)(不捕获上下文)的情况下会出现死锁。
Result
/ context死锁是一个非常常见的死锁 - 可能是最常见的死锁。但它不是那里唯一的僵局。
Here's an example如果你在 async
代码中阻止(没有上下文),就会出现更加困难的僵局。不过,这种情况更为罕见。
答案 1 :(得分:0)
我会以相反的方式给你一个简短的答案:如何产生僵局:
让我们从执行同步的Click
处理程序开始,并将一些异步调用卸载到单独的Task,然后等待结果
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var t = Task.Run(() => DeadlockProducer(sender as MenuItem));
var result = t.Result;
}
private async Task<int> DeadlockProducer(MenuItem sender)
{
await Task.Delay(1);
Dispatcher.Invoke(() => sender.Header = "Life sucks");
return 0;
}
异步方法正在做另一件坏事:它试图同步进行一些UI更改,但UI线程仍在等待任务结果......
基本上,Task.Run
已经完成了一半,并且可以用于很多格式良好的异步代码,但是有很多方法可以解决它,所以它不是一个可靠的解决方案。
此示例代码是在WPF的上下文中编写的,但我想ASP.Net可能会被滥用以产生类似的行为。