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

Yel*_*yev 4 c# asynchronous task-parallel-library async-await

我在Jon Skeet的"C#深度.第3版"中找到了以下示例:

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);
}
Run Code Online (Sandbox Code Playgroud)

我希望这段代码应该死锁,但事实并非如此.

在我看来,它的工作原理如下:

  1. Main方法GetPageLengthAsync在主线程内同步调用.
  2. GetPageLengthAsync使异步请求和immediatelly返回Task<int>Main说:"等一会儿,我会回到你的第二个int类型".
  3. Main继续执行并偶然lengthTask.Result导致主线程阻塞并等待lengthTask完成其工作.
  4. GetStringAsync完成并等待主线程可用于执行Length并开始继续.

但似乎我误解了一些事情.
为什么这段代码没有死锁?有关await/async死锁的Stackoverflow问题中的代码似乎也是这样,但死锁.

Pan*_*vos 10

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

在GUI应用程序中,由于UI线程被锁定,因此您将遇到死锁.Result.await我们将永远等待这个电话结束.

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

PS for VS 15.3:

Visual Studio 2017 15.3 Preview 2(gasp)允许异步主应用程序.有了它,你可以写:

public static Task Main()
{
    var length = await GetPageLengthAsync("http://csharpindepth.com");
    Console.WriteLine(length);
}
Run Code Online (Sandbox Code Playgroud)