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

Why doesn't calling Task<T>.Result deadlock?

在几个月前阅读 this post 之后,我变得偏执地想得到 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) 加了它)?

Task.Result 只会在某些 SynchronizationContext 出现时阻塞。在控制台应用程序中没有这样的应用程序,因此在 ThreadPool 上安排了延续。就像您使用 ConfigureAwait(false).

时一样

例如,在 UI 线程中,有一个可以将延续安排到单个 UI 线程。如果您使用 UI 线程与 Task.Result 同步等待,对于只能在 UI 线程上完成的任务,您会遇到死锁。

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

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


关于 HttpClient 的(以及大多数 .NET 的)Task-returning 方法:它们在技术上不是异步的。他们不使用 asyncawait 关键字。他们只是 return 一项任务。通常是 Task.Factory.FromAsync 的包装器。所以它可能 "safe" 阻止了他们。