The Short:

  1. Deadlocks
  2. Threadpool Starvation

The Long

For the purposes of this not-so-in-depth post, consider the following the same evil:

  • .Wait()
  • .Result
  • .GetResult()

There is nuance between them but feel free to read about them on your own.

The Documentation Says No

GetAwaiter() returns a TaskAwater object and if we peek into the documentation it says:

"This API supports the product infrastructure and is not intended to be used directly from your code."

The Documentation Says No, Again

GetResult() in the documentation also says:

"This API supports the product infrastructure and is not intended to be used directly from your code."

Deadlocks Can Happen

There are some very smart people with some very good answers but in short deadlocks happen because:

  1. The thread doing the work is now blocked due to a .Result and waiting for the call to come back.
  2. When the async task returns from the work, there's no thread to complete the work as it is stuck at .Result.

It's a little more complex due to SynchronizationContext and how .NET Core runs differently from an ASP.NET application which runs differently from a regular console application. Linking again, this fantastic writeup by Eke Péter goes much more in depth.

"But I've Never Seen a Deadlock Happen!"

Throw enough calls to simulate a high workload or call from a UI thread.

Threadpool Starvation

Now that we get the gist of how deadlocks happen, we can apply this to a threadpool. If we run enough load and have enough threads in the pool waiting for their own .Result calls, then eventually there will be no thread left to actually do the returned work.

Nope, same reasons as above. Sure in your weekend project it might be fine, but at 5:15pm on a Friday when there's a deadlock and you're trawling through error logs and memory dumps, maybe not. But if that's what you're into, I'm not shaming.

Alternatively if you'd like to make an HTTP request I'd like to think you have two options when you're currently using HttpClient in a synchronous codebase. I'm sure there are more, but these are easy to argue:

  1. Use another way to perform the HTTP request. Either a third party library or an older .NET way such as HttpWebRequest.
  2. Embrace the viral nature of async and let it propagate through your code.

To Finish

It's rough when you're trying to integrate an async piece of code into an existing (possibly legacy) synchronous codebase but I hope that this light brush of knowledge will help you understand why it can be scary to blindly throw around .GetAwaiter().GetResult() or any other async blocking call.

There is a lot more to dive into too, especially around SynchronizationContext and ConfigureAwait(false). Try a good bite into those if you're hungry for more understanding.

References:

Understanding Async, Avoiding Deadlocks in C#
You ran into some deadlocks, you are trying to write async code the proper way or maybe you’re just curious. Somehow you ended up here, and you want to fix a deadlock or improve your code. I’ll try…
davidfowl/AspNetCoreDiagnosticScenarios
This repository has examples of broken patterns in ASP.NET Core applications - davidfowl/AspNetCoreDiagnosticScenarios
Is Task.Result the same as .GetAwaiter.GetResult()?
I was recently reading some code that uses a lot of async methods, but then sometimes needs to execute them synchronously. The code does: Foo foo = GetFooAsync(...).GetAwaiter().GetResult(); Is t...
GetAwaiter().GetResult() vs GetAwaiter()?
Most devs are using GetAwaiter().GetResult(), and I can barely dig up examples using it with without the GetResult() part, however it is illogical to use GetResult() when we don’t need the result....