5

I have found the following example in Jon Skeet's "C# in depth. 3rd edition":

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);
}

I expected that this code would deadlock, but it does not.

As I see it, it works this way:

  1. Main method calls GetPageLengthAsync synchronously within the main thread.
  2. GetPageLengthAsync makes an asynchronous request and immediately returns Task<int> to Main saying "wait for a while, I will return you an int in a second".
  3. Main continues execution and stumbles upon lengthTask.Result which causes the main thread to block and wait for lengthTask to finish its work.
  4. GetStringAsync completes and waits for main thread to become available to execute Length and start continuation.

But it seems like I misunderstand something. Why doesn't this code deadlock?

The code in this StackOverflow question about await/async deadlock seems to do the same, but deadlocks.

CarenRose
  • 1,266
  • 1
  • 12
  • 24
Yeldar Kurmangaliyev
  • 33,467
  • 12
  • 59
  • 101
  • for a deadlock you need two asynchronous activities competing for a shared resource. I don't understand why you think that is the case here. – Jodrell Jun 14 '17 at 12:01
  • @Jodrell it's a common gotcha in GUI/ASP.NET applications - .Result blocks the UI thread, so `await` can't return to the original synchronization context. Same for ASP.NET. Console and .NET Core applications, including ASP.NET Core have no sync context – Panagiotis Kanavos Jun 14 '17 at 12:02
  • 2
    @juharr the OP asked a valid question. You *can* get into deadlocks if you try this code in a WinForms, WPF or ASP.NET application – Panagiotis Kanavos Jun 14 '17 at 12:04

1 Answers1

13

await returns to the original synchronization context, whether that is the UI thread (in desktop UI applications) or the request context in ASP.NET (not core).

In a GUI application, you'd have a deadlock because the UI thread was locked by .Result. await would await forever for this call to finish.

Console applications and ASP.NET Core have no synchronization context, so calling .Result won't cause a deadlock.

PS for VS 15.3:

Visual Studio 2017 15.3 Preview 2 (gasp) allows asynchronous main applications. With it, You can write :

public static Task Main()
{
    var length = await GetPageLengthAsync("http://csharpindepth.com");
    Console.WriteLine(length);
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • anytime you use an STA I guess. RE VS2017 15.3 Preview 2, at last! Looks interesting. – Jodrell Jun 14 '17 at 12:07
  • @Jodrell It's not about the STA, the threadpool threads are MTA by design. It's about the way the synchronization context is implemented in different platforms. ASP.NET Core just doesn't use a request context like ASP.NET. Console applications don't need a synchronization context to begin with – Panagiotis Kanavos Jun 14 '17 at 12:10
  • 1
    @Jodrell check Stephen Cleary's [ASP.NET Core SynchronizationContext](https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html) for more pointers – Panagiotis Kanavos Jun 14 '17 at 12:12