Async Programming
ConfigureAwait
C# Programming
.NET
Task Asynchrony

Understanding ConfigureAwait

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

ConfigureAwait is a method on Task and Task<T> in .NET that controls whether the continuation after an await runs on the original synchronization context (such as the UI thread) or on a thread pool thread. Understanding when and why to use ConfigureAwait(false) is essential for writing performant async code and avoiding deadlocks.

How await Works by Default

When you await a task, the runtime captures the current SynchronizationContext (or TaskScheduler) and posts the continuation back to it. In a UI application (WPF, WinForms, MAUI), this means the code after await runs on the UI thread:

csharp
1async Task LoadDataAsync()
2{
3    // Runs on UI thread
4    var data = await httpClient.GetStringAsync("https://api.example.com/data");
5    // Continues on UI thread — can safely update UI
6    textBox.Text = data;
7}

This is convenient for UI code but comes with overhead and potential deadlock risks.

What ConfigureAwait Does

ConfigureAwait takes a single boolean parameter:

  • ConfigureAwait(true): Default behavior. Captures the current context and continues on it.
  • ConfigureAwait(false): Does not capture the context. The continuation runs on any available thread pool thread.
csharp
1async Task LoadDataAsync()
2{
3    // After this await, execution may continue on ANY thread pool thread
4    var data = await httpClient.GetStringAsync("https://api.example.com/data")
5        .ConfigureAwait(false);
6
7    // WARNING: Cannot safely access UI elements here
8    // textBox.Text = data; // This would throw on a non-UI thread
9    return data;
10}

When to Use ConfigureAwait(false)

Library Code

In library methods that do not interact with UI or ASP.NET context, always use ConfigureAwait(false):

csharp
1// Good: library method uses ConfigureAwait(false)
2public async Task<User> GetUserAsync(int id)
3{
4    var response = await _httpClient.GetAsync($"/api/users/{id}")
5        .ConfigureAwait(false);
6
7    var json = await response.Content.ReadAsStringAsync()
8        .ConfigureAwait(false);
9
10    return JsonSerializer.Deserialize<User>(json);
11}

Benefits:

  • Avoids deadlocks when called from synchronous contexts
  • Improves performance by not posting back to the original context
  • Reduces thread contention on the UI thread

Data Access Layers and Services

Any code that does not need access to the calling context should use ConfigureAwait(false):

csharp
1public async Task<List<Product>> GetProductsAsync()
2{
3    using var connection = new SqlConnection(_connectionString);
4    await connection.OpenAsync().ConfigureAwait(false);
5
6    var products = await connection.QueryAsync<Product>("SELECT * FROM Products")
7        .ConfigureAwait(false);
8
9    return products.ToList();
10}

When NOT to Use ConfigureAwait(false)

UI Event Handlers

In UI event handlers where you need to update controls after the await:

csharp
1// DO NOT use ConfigureAwait(false) here
2private async void Button_Click(object sender, EventArgs e)
3{
4    var data = await GetDataAsync(); // Need to return to UI thread
5    textBox.Text = data;             // Updates UI control
6    progressBar.Visible = false;
7}

ASP.NET Core

In ASP.NET Core, there is no SynchronizationContext, so ConfigureAwait(false) has no effect. You can omit it, though including it does no harm:

csharp
1// ASP.NET Core: ConfigureAwait(false) is optional but harmless
2public async Task<IActionResult> GetUsers()
3{
4    var users = await _userService.GetUsersAsync(); // No context to capture
5    return Ok(users);
6}

The Deadlock Problem

The most critical reason for ConfigureAwait(false) is preventing deadlocks. This happens when synchronous code blocks on an async method that tries to return to the captured context:

csharp
1// DEADLOCK EXAMPLE (WPF/WinForms/.NET Framework ASP.NET)
2public void LoadData()
3{
4    // .Result blocks the UI thread
5    var data = GetDataAsync().Result; // DEADLOCK!
6}
7
8async Task<string> GetDataAsync()
9{
10    // Tries to resume on UI thread, but it's blocked by .Result
11    var result = await httpClient.GetStringAsync("https://api.example.com/data");
12    return result;
13}

The fix — use ConfigureAwait(false) in the async method:

csharp
1async Task<string> GetDataAsync()
2{
3    var result = await httpClient.GetStringAsync("https://api.example.com/data")
4        .ConfigureAwait(false); // Resumes on thread pool, no deadlock
5    return result;
6}

The better fix — do not call .Result or .Wait() on async code. Use await all the way up.

ConfigureAwait in .NET 8+

Starting with .NET 8, you can set ConfigureAwait behavior globally using ConfigureAwaitOptions:

csharp
1// .NET 8+: suppress throwing on cancellation
2await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
3
4// Combine options
5await task.ConfigureAwait(
6    ConfigureAwaitOptions.ContinueOnCapturedContext |
7    ConfigureAwaitOptions.ForceYielding
8);

Common Pitfalls

  • Inconsistent usage in a call chain: If one method in a chain uses ConfigureAwait(false) but an earlier one does not, the deadlock can still occur. Apply it consistently throughout library code.
  • Using in event handlers: Applying ConfigureAwait(false) in a UI event handler and then trying to update UI elements causes a cross-thread exception.
  • Forgetting after every await: You need ConfigureAwait(false) after each await in a method, not just the first one. Each await independently captures context.
  • ASP.NET Framework vs Core: The classic ASP.NET Framework has a SynchronizationContext and is prone to deadlocks. ASP.NET Core does not. Know which you are targeting.

Summary

ContextUse ConfigureAwait(false)?
Library codeYes, always
Data access / servicesYes
UI event handlersNo
ASP.NET Core controllersOptional (no effect)
ASP.NET Framework controllersYes (prevents deadlocks)
  • ConfigureAwait(false) prevents deadlocks and improves performance in library code
  • Never use it when you need to return to the UI thread
  • Apply it after every await in a method, not just the first
  • The root cause of most deadlocks is calling .Result or .Wait() on async code — prefer await throughout

Course illustration
Course illustration

All Rights Reserved.