15 minlesson

ConfigureAwait

ConfigureAwait

ConfigureAwait controls where code resumes after an await. Understanding it is crucial for library authors and for avoiding deadlocks.

The Synchronization Context Problem

In UI applications and ASP.NET (classic), there's a "synchronization context" that captures the current thread:

csharp
1// In a UI application
2async void Button_Click(object sender, EventArgs e)
3{
4 // Running on UI thread
5 var data = await FetchDataAsync();
6
7 // After await, BACK on UI thread (captured context)
8 lblResult.Text = data; // Safe - on UI thread
9}

How ConfigureAwait Works

csharp
1// Default: Resume on captured context
2await SomeMethodAsync();
3
4// ConfigureAwait(true): Same as default
5await SomeMethodAsync().ConfigureAwait(true);
6
7// ConfigureAwait(false): Resume on ANY thread
8await SomeMethodAsync().ConfigureAwait(false);

The Deadlock Problem

Without ConfigureAwait(false), you can cause deadlocks:

csharp
1// DEADLOCK in UI/ASP.NET classic!
2public string GetData()
3{
4 // This blocks the UI thread
5 return GetDataAsync().Result; // DEADLOCK!
6}
7
8private async Task<string> GetDataAsync()
9{
10 // This tries to resume on UI thread (which is blocked)
11 await httpClient.GetStringAsync(url);
12 return "data";
13}

The deadlock occurs because:

  1. GetData() blocks the UI thread waiting for the task
  2. GetDataAsync() awaits and captures the UI context
  3. After the HTTP call, it tries to resume on UI thread
  4. UI thread is blocked waiting, so it can never resume

Solution: ConfigureAwait(false)

csharp
1private async Task<string> GetDataAsync()
2{
3 // Resume on any available thread pool thread
4 await httpClient.GetStringAsync(url).ConfigureAwait(false);
5 return "data"; // May run on different thread
6}

When to Use ConfigureAwait(false)

Library Code

Always use ConfigureAwait(false) in library code:

csharp
1// In a library/NuGet package
2public async Task<Result> ProcessAsync()
3{
4 var data = await FetchAsync().ConfigureAwait(false);
5 var parsed = await ParseAsync(data).ConfigureAwait(false);
6 return await TransformAsync(parsed).ConfigureAwait(false);
7}

Why? Library code shouldn't assume a specific context. The caller may not have one, or capturing it may cause issues.

Application Code

In application code (UI, web controllers), it depends:

csharp
1// UI code - DON'T use ConfigureAwait(false) if you need UI access
2async void Button_Click(object sender, EventArgs e)
3{
4 var data = await FetchDataAsync(); // No ConfigureAwait
5 lblResult.Text = data; // Need to be on UI thread!
6}
7
8// ASP.NET Core - Context doesn't matter (no SynchronizationContext)
9public async Task<IActionResult> GetData()
10{
11 var data = await _service.GetDataAsync(); // ConfigureAwait optional
12 return Ok(data);
13}

Modern .NET: Less Relevant

In .NET Core and .NET 5+:

  • ASP.NET Core has no SynchronizationContext
  • Console apps have no SynchronizationContext
  • ConfigureAwait(false) is mostly a no-op

Still important for:

  • Libraries that may be used in WPF/WinForms
  • .NET Framework applications
  • Code that might block on async (though you shouldn't)

ConfigureAwait in Loops

Apply to each await:

csharp
1async Task ProcessItemsAsync(List<string> items)
2{
3 foreach (var item in items)
4 {
5 // Each await needs ConfigureAwait
6 var data = await FetchAsync(item).ConfigureAwait(false);
7 await ProcessAsync(data).ConfigureAwait(false);
8 }
9}

ConfigureAwait with WhenAll/WhenAny

csharp
1// ConfigureAwait on the WhenAll, not individual tasks
2var tasks = urls.Select(url => FetchAsync(url));
3await Task.WhenAll(tasks).ConfigureAwait(false);

What ConfigureAwait Does NOT Do

csharp
1// MYTH: ConfigureAwait(false) makes the awaited method run on background thread
2await DoWorkAsync().ConfigureAwait(false); // DoWorkAsync runs wherever it runs
3
4// TRUTH: It only affects where code AFTER the await resumes

Code Analysis Rules

Use analyzers to enforce ConfigureAwait:

xml
1<!-- In .csproj for library projects -->
2<PropertyGroup>
3 <AnalysisMode>Recommended</AnalysisMode>
4</PropertyGroup>

Common rule: CA2007 - Consider calling ConfigureAwait on the awaited task.

Summary Table

ScenarioRecommendation
Library codeAlways use ConfigureAwait(false)
ASP.NET CoreOptional (no context)
Console appsOptional (no context)
WPF/WinForms - need UIDon't use ConfigureAwait(false)
WPF/WinForms - no UI accessUse ConfigureAwait(false)

Key Takeaways

  • ConfigureAwait(false) prevents capturing sync context
  • Prevents deadlocks when blocking on async code
  • Always use in library code
  • Less important in .NET Core/5+ (no context in ASP.NET Core)
  • Only affects where code resumes, not where it runs
  • Apply to every await in a method if using it