Describe a scenario where using `async` and `await` in C# might inadvertently lead to a deadlock, even without explicit locks. Explain how this can happen and how to prevent it.

.NET interview question for Advanced practice.

Answer

A deadlock can occur with async and await when code synchronously blocks on a Task (using .Result or .Wait()) in an environment with a single-threaded SynchronizationContext, such as a UI application (WinForms, WPF) or a classic ASP.NET request. Scenario: 1. An event handler (e.g., a button click) on the UI thread calls an async method. 2. Inside the event handler, the code calls .Wait() or .Result on the task returned by the async method. This blocks the UI thread until the task completes. 3. The async method performs an await on an operation. When the operation completes, the await tries to resume execution on the captured SynchronizationContext (the UI thread). 4. A deadlock occurs: The UI thread is blocked waiting for the task to finish, but the task cannot finish because it is waiting for the UI thread to become available to continue execution. Example (Illustrative): csharp // In a UI application (e.g., button click handler) private void MyButtonClick(object sender, EventArgs e) { // This line causes a deadlock. var data = GetDataAsync().Result; myLabel.Text = data; } private async Task<string GetDataAsync() { // Awaits an operation, capturing the UI context. await Task.Delay(1000); return "Data loaded"; // This continuation needs the UI thread, which is blocked. } Preventing the Deadlock: 1. Go async all the way: The best practice is to use await instead of synchronously blocking. The calling method should also be marked async. private async void MyButtonClick(...) { var data = await GetDataAsync(); ... } 2. Use ConfigureAwait(false): In library code that doesn't need to return to the original context (e.g., it doesn't update the UI), use await myOperation.ConfigureAwait(false);. This tells the await not to resume on the captured context, which avoids the deadlock by allowing the continuation to run on a thread pool thread.

Explanation

This type of deadlock is especially common in UI or classic ASP.NET applications which have a single-threaded SynchronizationContext.

Related Questions