为啥不调用 Task<T>.Result 死锁?
Posted
技术标签:
【中文标题】为啥不调用 Task<T>.Result 死锁?【英文标题】:Why doesn't calling Task<T>.Result deadlock?为什么不调用 Task<T>.Result 死锁? 【发布时间】:2015-07-06 23:21:54 【问题描述】:几个月前阅读this 的帖子后,我开始偏执地想要得到Task<T>
的Result
,并不停地用ConfigureAwait(false)
或Task.Run
包裹我所有的电话。但是,由于某种原因,以下代码成功完成:
public static void Main(string[] args)
var arrays = DownloadMany();
foreach (var array in arrays);
IEnumerable<byte[]> 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)
填充了它)?
【问题讨论】:
顺便说一句.. 在需要的地方不阻塞和使用 await 比在任何地方使用ConfigureAwait
要简单得多。
@i3arnon 没错,但我正在编写一个支持同步和异步调用者的 API。
@JamesKo:我建议异步方法公开异步 API。但是,如果您必须同时支持同步和异步(例如,为了向后兼容),那么您可能会发现我最近的 MSDN article on brownfield async 很有用。
【参考方案1】:
Task.Result
只会在某些SynchronizationContext
s 存在时阻止。在控制台应用程序中没有一个,因此在ThreadPool
上安排了继续。就像您使用 ConfigureAwait(false)
时一样。
例如,在 UI 线程中,有一个将延续计划到单个 UI 线程。如果您使用 UI 线程与 Task.Result
同步等待,那么在只能在 UI 线程上完成的任务上,您就会遇到死锁。
此外,死锁取决于GetByteArrayAsync
的实现。如果它是异步方法并且它的等待不使用ConfigureAwait(false)
,则只能死锁。
如果您愿意,您可以使用 Stephen Cleary 的 AsyncContext
,它将适当的 SynchronizationContext
添加到您的控制台应用程序中,以测试您的代码是否可以在 UI 应用程序(或 ASP.Net)中阻塞。
关于HttpClient
(和大多数.NET)的任务返回方法:它们在技术上不是异步的。他们不使用async
和await
关键字。他们只是返回一个任务。通常是Task.Factory.FromAsync
的包装器。所以无论如何它可能是“安全的”阻止它们。
【讨论】:
这很奇怪。我认为这仅适用于异步/等待。这里没有继续,所以我不明白在不同的线程上会安排什么。此外,如果Result
没有 阻塞,正如您所建议的,返回的方法是什么?空数组?
@Asad 结果被阻止。它只是不阻塞单个 UI 线程(因为这不是 UI 应用程序)。它阻塞了主线程。
@i3arnon 不清楚您在说什么 UI 线程。我的观点是,Result
将始终处于阻塞状态,无论可用的同步上下文如何。如果该方法返回某事物的Task
,则必须在Task.Result
的getter 向您返回值之前解决该事物。至少在我看来,这是在 UI 应用程序还是控制台应用程序中运行并不重要。
@Asad 在 UI 应用程序(winforms、wpf)中有一个可以与 UI 交互的 UI 线程。如果任何其他线程这样做,它将获得异常。 SC 确保继续在该线程上运行。
这里的关键是,如果您在 UI 线程上的异步方法上调用 Result 并且不使用 ConfigureAwait(false)。因此,UI 线程阻塞了异步调用,该调用正在等待 UI 线程在返回之前释放。以上是关于为啥不调用 Task<T>.Result 死锁?的主要内容,如果未能解决你的问题,请参考以下文章
为啥要在 C# 中使用 Task<T> 而不是 ValueTask<T>?
为啥使用 IAsyncEnumerable 比返回 async/await Task<T> 慢?
使用 IMemoryCache 缓存结果 Task<T> 的正确方法
为啥我不能在我的 ArrayList<T> 上调用 Collections.sort()?
如果 Task<HttpResponseMessage>.IsCompleted 为真,为啥 Task<HttpResponseMessage>.Result 会抛出异常?