Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
C#

C#: Async/Await Best Practices – Avoid Common Mistakes

- 22.03.26 - ErcanOPAK

⚡ Async Done Right

Async/await is powerful but tricky. Common mistakes cause deadlocks, performance issues, bugs. Learn to do it right.

❌ Mistake 1: Async Void

// ❌ BAD: Async void (only for event handlers!)
public async void ProcessData()
{
    await DoWorkAsync();
}
// Problems:
// - Can't await it
// - Exceptions crash app
// - No way to know when it completes

// ✅ GOOD: Async Task
public async Task ProcessDataAsync()
{
    await DoWorkAsync();
}
// Can await, catch exceptions, compose operations

// Exception: Event handlers MUST be async void
private async void Button_Click(object sender, EventArgs e)
{
    try
    {
        await ProcessDataAsync();
    }
    catch (Exception ex)
    {
        // Handle exception - can't let it bubble up
        Logger.LogError(ex);
    }
}

❌ Mistake 2: Blocking on Async (Deadlock)

// ❌ BAD: Deadlock in UI or ASP.NET (pre-Core)
public void ProcessData()
{
    var result = GetDataAsync().Result;  // DEADLOCK!
    // Or
    GetDataAsync().Wait();  // DEADLOCK!
}

async Task GetDataAsync()
{
    await Task.Delay(1000);
    return "data";
}

// Why deadlock?
// 1. .Result blocks current thread
// 2. await tries to resume on same thread
// 3. Thread is blocked waiting for await
// 4. Deadlock!

// ✅ GOOD: Async all the way
public async Task ProcessDataAsync()
{
    var result = await GetDataAsync();
}

// Or in console app / ASP.NET Core (no sync context)
public void Main()
{
    var result = GetDataAsync().GetAwaiter().GetResult();  // OK in console
}

❌ Mistake 3: Not Using ConfigureAwait(false)

// ❌ In library code: Captures context unnecessarily
public async Task GetDataAsync()
{
    await httpClient.GetAsync(url);  // Resumes on original context
    return data;
}

// ✅ GOOD: Library code should use ConfigureAwait(false)
public async Task GetDataAsync()
{
    await httpClient.GetAsync(url).ConfigureAwait(false);
    return data;
}

// Rule of thumb:
// - UI code: Don't use ConfigureAwait (need UI thread)
// - Library code: Always ConfigureAwait(false) (performance)
// - ASP.NET Core: Doesn't matter (no sync context)

❌ Mistake 4: Fire and Forget

// ❌ BAD: Exceptions swallowed
public void DoWork()
{
    _ = ProcessDataAsync();  // Fire and forget - exception lost!
}

// ✅ GOOD: Await or explicitly handle
public async Task DoWorkAsync()
{
    await ProcessDataAsync();
}

// Or if truly fire-and-forget, handle exceptions
public void DoWork()
{
    _ = ProcessDataSafelyAsync();
}

private async Task ProcessDataSafelyAsync()
{
    try
    {
        await ProcessDataAsync();
    }
    catch (Exception ex)
    {
        Logger.LogError(ex);
    }
}

✅ Parallel Async Operations

// ❌ BAD: Sequential (6 seconds total)
var user = await GetUserAsync();      // 2 seconds
var orders = await GetOrdersAsync();  // 2 seconds
var products = await GetProductsAsync();  // 2 seconds

// ✅ GOOD: Parallel (2 seconds total)
var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();
var productsTask = GetProductsAsync();

await Task.WhenAll(userTask, ordersTask, productsTask);

var user = await userTask;
var orders = await ordersTask;
var products = await productsTask;

// Or even better
var (user, orders, products) = await (
    GetUserAsync(),
    GetOrdersAsync(),
    GetProductsAsync()
);

✅ Cancellation Tokens

// Always accept CancellationToken
public async Task GetDataAsync(CancellationToken ct = default)
{
    // Pass to async operations
    var response = await httpClient.GetAsync(url, ct);
    
    // Check periodically in loops
    foreach (var item in items)
    {
        ct.ThrowIfCancellationRequested();
        await ProcessAsync(item, ct);
    }
    
    return data;
}

// Usage with timeout
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var data = await GetDataAsync(cts.Token);

✅ Golden Rules

  • Async all the way: Don’t block on async code
  • Return Task, not void: Except event handlers
  • Use ConfigureAwait(false): In library code
  • Accept CancellationToken: For all async methods
  • Parallel when independent: Use Task.WhenAll
  • Suffix with Async: Name methods FooAsync

⚠️ Common Traps

// ❌ Capturing loop variable
foreach (var item in items)
{
    tasks.Add(Task.Run(() => Process(item)));  // Captures 'item'!
}

// ✅ Copy to local variable
foreach (var item in items)
{
    var local = item;
    tasks.Add(Task.Run(() => Process(local)));
}

// ❌ Async lambda in LINQ (doesn't await!)
items.Select(async item => await ProcessAsync(item));

// ✅ Use Task.WhenAll
var tasks = items.Select(item => ProcessAsync(item));
await Task.WhenAll(tasks);

“App froze randomly. Found .Result blocking async code. Changed to async all the way. No more freezes. Then made parallel calls with Task.WhenAll. API response time: 5s → 1s. Async is powerful when done right.”

— C# Architect

Related posts:

How to solve "Response.Redirect cannot be called in a Page callback" for DevExpress Components

C# — WeakReference Prevents Hidden Memory Leaks

List.ForEach Hides Exceptions

Post Views: 6

Post navigation

C#: Use LINQ Efficiently – Avoid Common Performance Pitfalls
C#: Use Span for High-Performance Memory Operations

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

April 2026
M T W T F S S
 12345
6789101112
13141516171819
20212223242526
27282930  
« Mar    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (858)
  • Get the First and Last Word from a String or Sentence in SQL (836)
  • How to select distinct rows in a datatable in C# (805)
  • How to make theater mode the default for Youtube (751)
  • Add Constraint to SQL Table to ensure email contains @ (578)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (564)
  • Average of all values in a column that are not zero in SQL (531)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (489)
  • Find numbers with more than two decimal places in SQL (447)

Recent Posts

  • C#: Use Init-Only Setters for Immutable Objects After Construction
  • C#: Use Expression-Bodied Members for Concise Single-Line Methods
  • C#: Enable Nullable Reference Types to Eliminate Null Reference Exceptions
  • C#: Use Record Types for Immutable Data Objects
  • SQL: Use CTEs for Readable Complex Queries
  • SQL: Use Window Functions for Advanced Analytical Queries
  • .NET Core: Use Background Services for Long-Running Tasks
  • .NET Core: Use Minimal APIs for Lightweight HTTP Services
  • Git: Use Cherry-Pick to Apply Specific Commits Across Branches
  • Git: Use Interactive Rebase to Clean Up Commit History Before Merge

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (858)
  • Get the First and Last Word from a String or Sentence in SQL (836)
  • How to select distinct rows in a datatable in C# (805)
  • How to make theater mode the default for Youtube (751)

Recent Posts

  • C#: Use Init-Only Setters for Immutable Objects After Construction
  • C#: Use Expression-Bodied Members for Concise Single-Line Methods
  • C#: Enable Nullable Reference Types to Eliminate Null Reference Exceptions
  • C#: Use Record Types for Immutable Data Objects
  • SQL: Use CTEs for Readable Complex Queries

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com