Phase 22 of 30 · Topic 22.3

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.