Async Best Practices

Back

Loading concept...

🎯 Async Best Practices in C#

The Art of Writing Smooth, Safe Async Code


🎬 The Story: The Busy Restaurant Kitchen

Imagine you’re the head chef of a very busy restaurant kitchen. You have multiple dishes cooking at once—pasta on one burner, soup simmering on another, and bread baking in the oven.

Now, here’s the thing: you can’t stand in front of the pasta and wait for it to boil. If you do, everything else burns! 🔥

That’s exactly what happens with async programming. Your code can start multiple “cooking tasks” without blocking the main kitchen (your UI thread). But like any busy kitchen, there are rules to keep things running smoothly.

Today, we’ll learn the 4 Golden Rules of async programming:

  1. ConfigureAwait – Know when to come back to the main kitchen
  2. Async Exception Handling – Catch problems before they explode
  3. Async Void Pitfalls – The dangerous silent worker
  4. Deadlock Prevention – Never let your kitchen freeze up

🔧 1. ConfigureAwait: Where Do You Continue?

The Simple Idea

When you use await, your code pauses and waits. When it resumes, where should it continue?

Think of it like this: You ask your helper to go get vegetables from the storage room. When they come back:

  • Option A: They must find YOU specifically and hand you the vegetables (original context)
  • Option B: They can give the vegetables to ANY available chef (any thread)
// Option A: Come back to the original thread (UI apps)
await GetDataAsync();

// Option B: Continue on any available thread (library code)
await GetDataAsync().ConfigureAwait(false);

When to Use What?

graph TD A["Are you in a library/backend?"] -->|Yes| B["Use ConfigureAwait false"] A -->|No| C["Are you updating UI?"] C -->|Yes| D[Don't use ConfigureAwait] C -->|No| E["Use ConfigureAwait false"]

Real Example

// ✅ In a class library - no UI work
public async Task<string> FetchDataAsync()
{
    var response = await httpClient
        .GetAsync(url)
        .ConfigureAwait(false);

    return await response.Content
        .ReadAsStringAsync()
        .ConfigureAwait(false);
}

// ✅ In UI code - needs to update screen
public async void ButtonClicked()
{
    var data = await FetchDataAsync();
    myLabel.Text = data; // Must be on UI thread!
}

💡 Golden Rule

Library code? Use ConfigureAwait(false) everywhere.

UI code that updates the screen? Don’t use it.


🚨 2. Async Exception Handling

The Problem: Exceptions Can Get Lost!

In our kitchen story, imagine a helper goes to the storage room and finds a FIRE! 🔥 But instead of telling you immediately, they just… disappear. You never know what happened!

That’s what can happen with async code if you don’t handle exceptions properly.

The Right Way to Catch Async Exceptions

// ✅ CORRECT: Using try-catch with await
public async Task ProcessOrderAsync()
{
    try
    {
        await CookPastaAsync();
        await PrepareGarlicBreadAsync();
    }
    catch (CookingException ex)
    {
        // Handle the problem!
        Console.WriteLine(quot;Kitchen fire: {ex.Message}");
    }
}

❌ What NOT to Do

// ❌ WRONG: Exception escapes silently!
public void ProcessOrderBad()
{
    try
    {
        CookPastaAsync(); // No await = exception lost!
    }
    catch (Exception ex)
    {
        // This NEVER catches the exception!
    }
}

Handling Multiple Tasks

When you have multiple dishes cooking at once:

// Multiple tasks? Use Task.WhenAll with try-catch
public async Task CookMealAsync()
{
    var tasks = new[]
    {
        CookPastaAsync(),
        MakeSauceAsync(),
        PrepareSaladAsync()
    };

    try
    {
        await Task.WhenAll(tasks);
    }
    catch (Exception ex)
    {
        // Check each task for its specific error
        foreach (var task in tasks)
        {
            if (task.IsFaulted)
            {
                Console.WriteLine(task.Exception?.Message);
            }
        }
    }
}

💡 Golden Rule

Always await your async calls inside try-catch.

No await = No exception catching!


⚠️ 3. Async Void Pitfalls

The Dangerous Silent Worker

Remember our kitchen helper? What if you had a helper who:

  • Never tells you when they’re done
  • Never reports problems
  • Just… does things in the background

That’s async void. It’s the most dangerous pattern in async C#!

Why Async Void is Bad

// ❌ DANGEROUS: async void
public async void DoSomethingBad()
{
    await Task.Delay(1000);
    throw new Exception("Fire!");
    // 💥 This crashes your whole app!
    // Nobody can catch this exception!
}

The Correct Way

// ✅ SAFE: async Task
public async Task DoSomethingGood()
{
    await Task.Delay(1000);
    throw new Exception("Fire!");
    // ✅ Caller can catch this!
}

// Usage:
try
{
    await DoSomethingGood();
}
catch (Exception ex)
{
    // We caught the fire! 🧯
}

The ONLY Exception

There’s exactly ONE place where async void is okay:

// ✅ Event handlers MUST be async void
private async void Button_Click(object sender, EventArgs e)
{
    try
    {
        await ProcessClickAsync();
    }
    catch (Exception ex)
    {
        // Handle error in the event handler itself!
        ShowErrorMessage(ex.Message);
    }
}

Quick Comparison

Pattern Exception Handling Can Await? Use When?
async Task ✅ Safe ✅ Yes Almost always!
async void ❌ Crashes app ❌ No Only event handlers

💡 Golden Rule

Never use async void except for event handlers.

In event handlers, wrap everything in try-catch!


🔒 4. Deadlock Prevention

The Frozen Kitchen

Imagine this nightmare scenario:

  1. You tell a helper: “Go get flour, then come back to ME”
  2. Meanwhile, you wait and do nothing
  3. The helper returns, but you’re blocking the only door!
  4. EVERYONE IS STUCK FOREVER 😱

This is a deadlock, and it can happen in async code!

How Deadlocks Happen

// ❌ DEADLOCK waiting to happen!
public void DeadlockExample()
{
    // You're on the UI thread, waiting...
    var result = GetDataAsync().Result; // 💥 BANG!
    // GetDataAsync wants to come back to UI thread
    // But YOU are blocking the UI thread!
    // Nobody can move = DEADLOCK
}

The Three Fixes

Fix 1: Use await (Best!)

// ✅ Use async all the way
public async Task SafeMethodAsync()
{
    var result = await GetDataAsync();
    // No blocking = no deadlock!
}

Fix 2: ConfigureAwait(false)

// ✅ Tell the task it doesn't need the original thread
public async Task<string> GetDataAsync()
{
    var response = await httpClient
        .GetAsync(url)
        .ConfigureAwait(false);
    // "Any thread is fine for me!"
    return await response.Content
        .ReadAsStringAsync()
        .ConfigureAwait(false);
}

Fix 3: Use Task.Run for CPU work

// ✅ Move blocking work off the UI thread
public async void Button_Click(object sender, EventArgs e)
{
    var result = await Task.Run(() =>
    {
        // Heavy work runs on background thread
        return CalculateSomethingHeavy();
    });

    label.Text = result;
}

Deadlock Warning Signs

graph TD A["Are you using .Result or .Wait?"] -->|Yes| B["🚨 DANGER!"] A -->|No| C["Are you using await?"] C -->|Yes| D[✅ You're safe!] C -->|No| E["Why not? Always await!"]

💡 Golden Rule

Never use .Result or .Wait() on UI or ASP.NET threads.

Async all the way = No deadlocks!


🏆 Summary: The 4 Golden Rules

Rule Do This ✅ Not This ❌
ConfigureAwait Use ConfigureAwait(false) in libraries Forget it in non-UI code
Exceptions Wrap awaits in try-catch Call async without await
Async Void Use async Task Use async void (except events)
Deadlocks Await properly Use .Result or .Wait()

🎯 Final Memory Trick

Think CEAD (like “seed” for growing safe code):

  • ConfigureAwait – Where to continue?
  • Exceptions – Always await in try-catch
  • Async void – Task only, never void!
  • Deadlocks – Never .Result, always await

You’ve got this! Your async kitchen is now safe, fast, and fire-free! 🎉

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.