Explain what `ConfigureAwait(false)` does and when to use it.
Quick Answer
`ConfigureAwait(false)` tells the awaiter not to capture and resume on the original `SynchronizationContext`, letting the continuation run on any thread-pool thread. This avoids unnecessary context marshaling (improving performance) and prevents the classic deadlock when synchronous code blocks on async work. Use it in library code where you don't need the original context; it's generally unnecessary in ASP.NET Core (no sync context) and undesirable in UI continuations that touch the UI.
Detailed Answer
ConfigureAwait(false) tells the awaited task not to capture and resume on the original synchronization context. This improves performance and avoids potential deadlocks in library code.
When to use:
- In library code (not UI code)
- When you don't need to return to the original context
- To improve performance
- To avoid deadlocks
Example:
// Understanding SynchronizationContext
public class SynchronizationContextExample
{
// WITHOUT ConfigureAwait(false) - captures context
public async Task GetDataWithContextAsync()
{
Console.WriteLine($"Before await - Thread: {Thread.CurrentThread.ManagedThreadId}");
// By default, await captures the current SynchronizationContext
await Task.Delay(1000);
// Resumes on the same context (same thread in UI apps)
Console.WriteLine($"After await - Thread: {Thread.CurrentThread.ManagedThreadId}");
return "Data with context";
}
// WITH ConfigureAwait(false) - doesn't capture context
public async Task GetDataWithoutContextAsync()
{
Console.WriteLine($"Before await - Thread: {Thread.CurrentThread.ManagedThreadId}");
// ConfigureAwait(false) tells it not to capture context
await Task.Delay(1000).ConfigureAwait(false);
// Can resume on any thread pool thread
Console.WriteLine($"After await - Thread: {Thread.CurrentThread.ManagedThreadId}");
return "Data without context";
}
}
// Library code example - SHOULD use ConfigureAwait(false)
public class DataLibrary
{
private readonly HttpClient httpClient = new HttpClient();
// Good: Library code using ConfigureAwait(false)
public async Task FetchDataAsync(string url)
{
// Library code doesn't need UI context
var response = await httpClient.GetAsync(url).ConfigureAwait(false);
// Still on thread pool thread (not UI thread)
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// Process data without needing UI thread
var processedData = ProcessData(content);
return processedData;
}
public async Task<List> FetchMultipleAsync(List urls)
{
var results = new List();
foreach (var url in urls)
{
// Each await uses ConfigureAwait(false)
var data = await FetchDataAsync(url).ConfigureAwait(false);
results.Add(data);
}
return results;
}
private string ProcessData(string data)
{
// CPU-bound processing
return data.ToUpper();
}
}
// UI/Application code example - DON'T use ConfigureAwait(false)
public class UserInterfaceCode
{
// Bad: Don't use ConfigureAwait(false) in UI code
public async Task UpdateUIAsync()
{
var library = new DataLibrary();
// Get data (library uses ConfigureAwait(false) internally)
var data = await library.FetchDataAsync("https://api.example.com/data");
// We're back on UI thread here, can update UI safely
// Don't use ConfigureAwait(false) here!
UpdateTextBox(data);
}
private void UpdateTextBox(string text)
{
// This needs to run on UI thread
Console.WriteLine($"Updating UI with: {text}");
}
}
// Deadlock prevention example
public class DeadlockExample
{
// This can cause a deadlock in UI apps
public string GetDataSync()
{
// .Result blocks and waits for task
// But task tries to resume on UI thread which is blocked
// DEADLOCK!
return GetDataAsync().Result;
}
private async Task GetDataAsync()
{
await Task.Delay(1000); // Tries to resume on UI thread
return "Data";
}
// Fix with ConfigureAwait(false)
public string GetDataSyncFixed()
{
return GetDataAsyncFixed().Result; // Still not ideal, but won't deadlock
}
private async Task GetDataAsyncFixed()
{
await Task.Delay(1000).ConfigureAwait(false); // Won't try to resume on UI thread
return "Data";
}
}
// Complete example showing best practices
public class BestPracticesExample
{
// Library/Service layer - use ConfigureAwait(false)
public class OrderService
{
public async Task GetOrderAsync(int orderId)
{
await Task.Delay(100).ConfigureAwait(false);
var order = await FetchFromDatabaseAsync(orderId).ConfigureAwait(false);
var details = await FetchOrderDetailsAsync(orderId).ConfigureAwait(false);
order.Details = details;
return order;
}
private async Task FetchFromDatabaseAsync(int id)
{
await Task.Delay(50).ConfigureAwait(false);
return new Order { Id = id, CustomerName = "John Doe" };
}
private async Task<List> FetchOrderDetailsAsync(int orderId)
{
await Task.Delay(50).ConfigureAwait(false);
return new List
{
new OrderDetail { ProductName = "Widget", Quantity = 2 }
};
}
}
// UI/Controller layer - DON'T use ConfigureAwait(false)
public class OrderController
{
private readonly OrderService orderService = new OrderService();
public async Task DisplayOrderAsync(int orderId)
{
// No ConfigureAwait(false) here - we need UI context
var order = await orderService.GetOrderAsync(orderId);
// Can safely update UI because we're on UI thread
Console.WriteLine($"Order for: {order.CustomerName}");
Console.WriteLine($"Items: {order.Details.Count}");
}
}
public class Order
{
public int Id { get; set; }
public string CustomerName { get; set; }
public List Details { get; set; }
}
public class OrderDetail
{
public string ProductName { get; set; }
public int Quantity { get; set; }
}
}
Guidelines:
-
Use
ConfigureAwait(false)in:- Library/framework code
- Service layer methods
- Any code that doesn't need to return to the original context
-
DON'T use
ConfigureAwait(false)in:- UI event handlers
- ASP.NET Core controllers (post .NET Core 2.0+ - no sync context anyway)
- Code that needs to update UI elements
- Top-level application code