Task.Result and .Wait() Deadlocks in C#: Why Sync-over-Async Hangs (video)

Calling .Result or .Wait() on a task can deadlock UI apps and starve ASP.NET Core of threads. Learn why sync-over-async hangs, how to fix it with async all the way, and what to do when you truly cannot await.

Read the full article

Transcript

Task.Result and .Wait() Deadlocks in C#: Why Sync-over-Async Hangs. Here's the short version.

Calling .Result or .Wait() on a Task looks like a quick way to use an async method from synchronous code. In a desktop app it can freeze the UI forever.

How the classic deadlock happens. First, the UI thread calls GetDataAsync().Result and blocks, waiting for the task. Second, inside GetDataAsync, an await finishes and wants to continue on the UI thread (the captured SynchronizationContext). Third, the UI thread is blocked waiting for the task, so the continuation can never run. Fourth, the task never completes, and the app hangs.

Here's an example: Blocks a thread and can deadlock in UI apps.

If you really cannot await. Change the interface to return Task. This is almost always worth the refactor. Use a synchronous API if the library offers one (many I/O libraries do). In library code, use ConfigureAwait(false) on every await so continuations do not need the caller's context. As a last resort, GetAwaiter().GetResult() instead of .Result.

Related traps. async void methods: exceptions cannot be caught by the caller; use async Task except for event handlers. Task.Run(() => SomethingAsync()).Result: still blocks, just on a different thread. Parallel.ForEach with async lambdas: the lambdas become async void; use Parallel.ForEachAsync instead.

Want to find these issues in your own code? Connect your GitHub repository to CodeInspector. You'll get findings with the file, line, severity, and a recommended fix, plus HTML and PDF reports for your team. Start free at codeinspector.app.

More videos

All videos