Skip to main content

ConfigureAwait Best Practices

Overview​

This document outlines the proper usage of ConfigureAwait() in the arolariu.ro codebase, explaining when to use it, when to omit it, and the rationale behind these decisions.

Quick Reference​

ContextRuleRationale
Production Code✅ Always use ConfigureAwait(false)Prevents deadlocks, improves performance
Test Methods❌ Never use ConfigureAwait()Bypasses test parallelization, unnecessary overhead
ASP.NET Controllers✅ Use ConfigureAwait(false)No synchronization context after first await
Library Code✅ Use ConfigureAwait(false)Should not assume execution context

Production Code Guidelines​

✅ DO: Use ConfigureAwait(false) in Library Code​

Why? Library code should not assume it will run on a specific synchronization context.

// ✅ CORRECT
public async Task<Invoice> GetInvoiceAsync(Guid id)
{
var data = await _repository.FetchAsync(id).ConfigureAwait(false);
var result = await ProcessDataAsync(data).ConfigureAwait(false);
return result;
}
// ❌ INCORRECT - Missing ConfigureAwait
public async Task<Invoice> GetInvoiceAsync(Guid id)
{
var data = await _repository.FetchAsync(id); // CA2007 warning
var result = await ProcessDataAsync(data); // CA2007 warning
return result;
}

✅ DO: Use ConfigureAwait(false) in ASP.NET Core​

Why? After the first await, ASP.NET Core doesn't restore the synchronization context, so you should explicitly use ConfigureAwait(false) for clarity and performance.

// ✅ CORRECT
[HttpGet("{id}")]
public async Task<ActionResult<Invoice>> GetInvoice(Guid id)
{
var invoice = await _service.GetInvoiceAsync(id).ConfigureAwait(false);
return Ok(invoice);
}

✅ DO: Chain ConfigureAwait(false) for All Awaits​

Why? Consistency prevents accidental context captures and makes code behavior predictable.

// ✅ CORRECT - All awaits use ConfigureAwait(false)
public async Task<ProcessedData> ProcessAsync(InputData data)
{
var step1 = await ValidateAsync(data).ConfigureAwait(false);
var step2 = await TransformAsync(step1).ConfigureAwait(false);
var step3 = await EnrichAsync(step2).ConfigureAwait(false);
return await FinalizeAsync(step3).ConfigureAwait(false);
}

Test Code Guidelines​

❌ DON'T: Use ConfigureAwait in Test Methods​

Why? Test frameworks manage their own synchronization contexts for proper parallelization.

// ✅ CORRECT - No ConfigureAwait in tests
[TestMethod]
public async Task GetInvoice_ValidId_ReturnsInvoice()
{
// Arrange
var expectedInvoice = InvoiceBuilder.CreateRandomInvoice();
_mockRepository.Setup(r => r.GetAsync(It.IsAny<Guid>()))
.ReturnsAsync(expectedInvoice);

// Act
var result = await _service.GetInvoiceAsync(Guid.NewGuid());

// Assert
Assert.IsNotNull(result);
Assert.AreEqual(expectedInvoice.id, result.id);
}
// ❌ INCORRECT - ConfigureAwait in test bypasses parallelization
[TestMethod]
public async Task GetInvoice_ValidId_ReturnsInvoice()
{
var result = await _service.GetInvoiceAsync(Guid.NewGuid()).ConfigureAwait(false);
Assert.IsNotNull(result);
}

Build Configuration for Tests​

Test projects automatically disable CA2007 warnings via Directory.Build.props:

<!-- sites/api.arolariu.ro/tests/Directory.Build.props -->
<PropertyGroup Label="Test Project Settings">
<NoWarn>$(NoWarn);CA2007</NoWarn>
</PropertyGroup>

This means:

  • ✅ Test code doesn't need ConfigureAwait calls
  • ✅ No CA2007 warnings in test files
  • ✅ Simpler, cleaner test code
  • ✅ Proper test parallelization behavior

Common Scenarios​

Scenario 1: Domain Service Methods​

// ✅ CORRECT
public async Task<Invoice> AnalyzeInvoiceAsync(Invoice invoice)
{
// All library/service code uses ConfigureAwait(false)
var validated = await ValidateDomainRulesAsync(invoice).ConfigureAwait(false);
var enriched = await EnrichWithAIAsync(validated).ConfigureAwait(false);
return await ApplyBusinessLogicAsync(enriched).ConfigureAwait(false);
}

Scenario 2: Repository/Data Access​

// ✅ CORRECT
public async Task<Invoice> GetByIdAsync(Guid id, Guid? userId)
{
var container = await GetContainerAsync().ConfigureAwait(false);
var response = await container.ReadItemAsync<Invoice>(
id.ToString(),
new PartitionKey(userId?.ToString() ?? string.Empty)
).ConfigureAwait(false);

return response.Resource;
}

Scenario 3: Exception Handling​

// ✅ CORRECT - ConfigureAwait in try-catch blocks
private async Task<Invoice> TryCatchAsync(ReturningInvoiceFunction function)
{
try
{
return await function().ConfigureAwait(false);
}
catch (ValidationException ex)
{
throw CreateAndLogValidationException(ex);
}
catch (DependencyException ex)
{
throw CreateAndLogDependencyException(ex);
}
}

Scenario 4: Integration Tests (Different Rules!)​

// ✅ CORRECT - Integration tests can omit ConfigureAwait
[TestMethod]
public async Task EndToEnd_CreateAndRetrieveInvoice_Success()
{
// Arrange
var client = _factory.CreateClient();
var invoice = CreateTestInvoice();

// Act
var createResponse = await client.PostAsJsonAsync("/api/invoices", invoice);
var getResponse = await client.GetAsync($"/api/invoices/{invoice.id}");

// Assert
createResponse.EnsureSuccessStatusCode();
getResponse.EnsureSuccessStatusCode();
}

Performance Implications​

With ConfigureAwait(false)​

  • ✅ Avoids unnecessary context switches
  • ✅ Better thread pool utilization
  • ✅ Prevents potential deadlocks in library code
  • ✅ Lower memory overhead (no context capture)

Without ConfigureAwait (in production)​

  • ❌ Potential deadlocks in synchronous-over-async code
  • ❌ Unnecessary context captures
  • ❌ Reduced throughput due to context switches
  • ❌ Higher memory allocation

Without ConfigureAwait (in tests)​

  • ✅ Correct behavior - test frameworks need context control
  • ✅ Proper test parallelization
  • ✅ Simpler, more readable test code

Analyzer Rules​

CA2007: Do not directly await a Task​

Severity: Warning (Production), Silent (Tests)

Configuration:

  • Production projects: <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
  • Test projects: <NoWarn>$(NoWarn);CA2007</NoWarn>

EditorConfig:

# Production code - enforce ConfigureAwait
[**/src/**/*.cs]
dotnet_diagnostic.CA2007.severity = warning

# Test code - disable ConfigureAwait requirement
[**/tests/**/*.cs]
dotnet_diagnostic.CA2007.severity = none

Migration Guide​

Fixing Existing Code​

Step 1: Run analyzer to find violations

dotnet build /p:EnforceCodeStyleInBuild=true

Step 2: Fix production code violations

// Before
await SomeMethodAsync();

// After
await SomeMethodAsync().ConfigureAwait(false);

Step 3: Remove ConfigureAwait from test code

// Before
await orchestrationService.CreateInvoiceObject(invoice, null).ConfigureAwait(false);

// After
await orchestrationService.CreateInvoiceObject(invoice, null);

References​


Summary​

ContextConfigureAwaitReason
Service layer✅ .ConfigureAwait(false)Library code
Repository layer✅ .ConfigureAwait(false)Data access code
Controllers✅ .ConfigureAwait(false)ASP.NET Core best practice
Domain logic✅ .ConfigureAwait(false)Pure business logic
Unit tests❌ OmitTest framework control
Integration tests❌ OmitTest framework control

Golden Rule:

  • Production = Always ConfigureAwait(false)
  • Tests = Never ConfigureAwait
// was this page useful?