The Async Deadlock Trap: Sync-over-Async (`.Result` / `.Wait()`)
1Concept
Calling `.Result` or `.Wait()` synchronously on a running async Task blocks the calling thread while it holds the SynchronizationContext. When the async method attempts to marshal back to the same blocked context, the application deadlocks.
2Architecture Diagram
UI Thread calls: var data = GetDataAsync().Result; (BLOCKED!)
│
GetDataAsync() finishes on background thread ──> Tries to resume on UI Thread
│
DEADLOCK! (UI thread is waiting for Task; Task is waiting for UI thread!)3Code Example
C# 13 & .NET 9
using System;
using System.Threading.Tasks;
public class DeadlockPreventionDemo
{
public static async Task<string> FetchPayloadAsync()
{
await Task.Delay(10);
return "Payload";
}
public static async Task Main()
{
// Safe: Always await all the way up!
string data = await FetchPayloadAsync();
Console.WriteLine($"Safely Awaited Data: {data}");
}
}4Expected Output
Safely Awaited Data: Payload
5Key Takeaways
- ✓Never call `.Result`, `.Wait()`, or `.GetAwaiter().GetResult()` in UI or legacy ASP.NET apps.
- ✓Async is viral: make the entire callstack `async Task` all the way to `Main` / Controller action.
- ✓Use `Task.Run(async () => ...)` only if forced to bridge legacy synchronous APIs.