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

Jam*_* Ko 7 c# deadlock asynchronous async-await dotnet-httpclient

几个月前读完这篇文章之后,我变得妄想得到Result一个Task<T>并且用一个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?
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

代码打印Still here!3次然后退出.这是否特定于HttpClient它是安全的Result(如在编写它的人已经用它ConfigureAwait(false))?

i3a*_*non 10

Task.Result只会在某些SynchronizationContexts 存在的情况下阻止.在控制台应用程序中没有一个如此延续安排在ThreadPool.就像他们使用时一样ConfigureAwait(false).

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

而且,死锁取决于执行情况GetByteArrayAsync.如果它是异步方法并且它等待不使用,则只能死锁ConfigureAwait(false).

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


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

  • @Asad结果正在阻止.它只是没有阻止单个UI线程(因为这不是一个UI应用程序).它阻止了主线程. (2认同)
  • 这里的关键是,如果在UI线程上的异步方法上调用Result并且不使用ConfigureAwait(false).因此,UI线程在异步调用上阻塞,该异步调用正在等待UI线程在返回之前释放. (2认同)