为什么这个异步/等待代码不会导致死锁?

问题描述:

我发现在乔恩斯基特的下面的例子“C#深入第三版”:为什么这个异步/等待代码不会导致死锁?

static async Task<int> GetPageLengthAsync(string url) 
{ 
    using (HttpClient client = new HttpClient()) 
    { 
     Task<string> fetchTextTask = client.GetStringAsync(url); 
     int length = (await fetchTextTask).Length; 
     return length; 
    } 
} 

public static void Main() 
{ 
    Task<int> lengthTask = GetPageLengthAsync("http://csharpindepth.com"); 
    Console.WriteLine(lengthTask.Result); 
} 

我希望此代码应僵局,但事实并非如此。

在我看来,它的工作原理是这样的:

  1. Main方法调用GetPageLengthAsync同步主线程中。
  2. GetPageLengthAsync发出异步请求并立即返回Task<int>Main说“等一会儿,我会在一秒钟内返回一个int”。
  3. Main继续执行并在lengthTask.Result上绊倒,导致主线程阻塞并等待lengthTask完成其工作。
  4. GetStringAsync完成并等待主线程可用来执行Length并开始继续。

但似乎我误解了一些东西。
为什么这段代码不会死锁? this * question about await/async deadlock中的代码似乎也是这样做的,但会造成死锁。

+0

对于死锁,您需要两个竞争共享资源的异步活动。我不明白你为什么这样认为。 – Jodrell

+0

@Jodrell这是GUI/ASP.NET应用程序中的一个常见问题 - .Result阻塞了UI线程,所以'await'无法返回到原始同步上下文。相同的ASP.NET。控制台和.NET Core应用程序(包括ASP.NET Core)没有同步上下文 –

+2

@juharr OP询问了一个有效的问题。如果您在WinForms,WPF或ASP.NET应用程序 –

await返回到原始同步上下文,无论是UI线程(在桌面UI应用程序中)还是ASP.NET(非核心)中的请求上下文。

在GUI应用程序中,由于UI线程被锁定了.Result,所以会产生死锁。这个电话完成后,await将永远等待。

控制台应用程序和ASP.NET核心没有同步上下文,因此调用.Result不会导致死锁。

PS VS为15.3:

Visual Studio 2017 15.3 Preview 2(喘气)允许异步主应用程序。有了它,您可以编写:

public static Task Main() 
{ 
    var length = await GetPageLengthAsync("http://csharpindepth.com"); 
    Console.WriteLine(length); 
} 
+0

中尝试使用此代码,则您可能*陷入死锁。 RE VS2017 15.3预览2,终于!看起来很有趣。 – Jodrell

+0

@Jodrell这不是关于STA,线程池线程是由设计MTA。这是关于同步上下文在不同平台中实现的方式。 ASP.NET Core不使用像ASP.NET这样的请求上下文。控制台应用程序并不需要同步方面具有 –

+1

开始@Jodrell检查斯蒂芬·克利里的[ASP.NET核心的SynchronizationContext(https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html)更多指针 –