Why is .GetAwaiter().GetResult() bad in C#?
Why is .GetAwaiter().GetResult(), or .Wait() or .Result bad? It ends up boiling down to deadlocks and threadpool starvation. This post gives a gentle, high up look at why this may happen.

The Short:
- Deadlocks
- 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 TaskAwaiter
object and if we peek into the documentation it says:
"This type is intended for compiler use only."
The Documentation Says No, Again
The GetResult()
documentation has been updated since this post was originally written. But in the PR to update said documentation, Stephen Toub from Microsoft outlines more of the "No":
Now, there's the related question of "what aboutGetAwaiter().GetResult()
on aTask
rather than on a configured awaiter, as those docs also say the same thing". When all of this support was introduced, the intent was in fact that no one should ever be using these directly, that they were purely for compiler consumption.
Deadlocks Can Happen
There are some very smart people with some very good answers but in short deadlocks happen because:
- The thread doing the work is now blocked due to a
.Result
and waiting for the call to come back. - 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.
"I'm using HttpClient (or other popular class/library) from synchronous code, can I just use .Result?"
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:
- Use another way to perform the HTTP request. Either a third party library or an older .NET way such as HttpWebRequest.
- Embrace the viral nature of async and let it propagate through your code.
The Quickest Example...
...With the least setup that I could get going. Credit to the example in this post that helped me a smidge.
- In Visual Studio, create a new MVC project
- Have your
HomeController
look like the code below - Run
public class HomeController : Controller
{
public ActionResult Index()
{
var githubTask = GetGitHubStringAsync();
var githubString = githubTask.Result.ToString();
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
public static async Task<object> GetGitHubStringAsync()
{
using (var client = new HttpClient())
{
// Technically does a 403, but that doesn't matter for our case as all we need is a HTTP response
var githubResult = await client.GetStringAsync(@"https://api.github.com/zen");
return githubResult;
}
}
}
The default index page should not load, and the browser will be left waiting.
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:


