为什么不调用Task .Result死锁?

几个月前阅读这篇文章之后,我变得很擅长获取TaskResult ,并且通过ConfigureAwait(false)Task.Run不断地将所有调用包裹Task.Run 。 但是,由于某种原因,以下代码成功完成:

 public static void Main(string[] args) { var arrays = DownloadMany(); foreach (var array in arrays); } IEnumerable DownloadMany() { string[] links = { "http://google.com", "http://microsoft.com", "http://apple.com" }; using (var client = new HttpClient()) { foreach (var uri in links) { Debug.WriteLine("Still here!"); yield return client.GetByteArrayAsync(uri).Result; // Why doesn't this deadlock? } } } 

代码打印Still here! 3次然后退出。 这是否特定于HttpClient ,可以安全地调用Result (如编写它的人用ConfigureAwait(false)填充它)?

Task.Result只会在某些SynchronizationContext存在时阻塞。 在控制台应用程序中,没有一个在ThreadPool上安排继续。 就像使用ConfigureAwait(false)

例如,在UI线程中,有一个调度单个UI线程的延续。 如果使用UI线程与Task.Result同步等待, Task.Result在UI线程上完成的任务就会出现死锁。

此外,死锁取决于GetByteArrayAsync的实现。 如果它是异步方法并且它等待不使用ConfigureAwait(false)则只能死锁。

如果您愿意,可以使用Stephen Cleary的AsyncContext ,它将适当的SynchronizationContext添加到您的控制台应用程序,以测试您的代码是否可以在UI应用程序(或ASP.Net)中阻止。


关于HttpClient (以及大多数.NET)的任务返回方法:它们在技术上并不是异步的。 他们不使用asyncawait关键字。 他们只是返回一个任务。 通常是Task.Factory.FromAsync的包装器。 所以无论如何它可能“安全”阻挡它们。