Back to Home

How to use async and await in C#

The async and await keywords in C# enable asynchronous programming. An async method can contain await expressions that suspend execution until an awaited task completes, allowing other work to proceed.

Methods marked with async typically return Task or Task<T>. The await keyword unwraps the result from a task without blocking the calling thread.

Async methods should have "Async" suffix by convention (e.g., GetDataAsync), and you should avoid async void except for event handlers.

Basic Async/Await Usage

C# Example Code
using System.Net.Http;

Console.WriteLine("Starting async operation...");

// Call async method with await
string result = await FetchDataAsync();
Console.WriteLine($"Result: {result}");

// Multiple async calls in sequence
await Task.Delay(1000);  // Wait 1 second
Console.WriteLine("One second passed");

// Running multiple tasks concurrently
Task<int> task1 = CalculateAsync(5);
Task<int> task2 = CalculateAsync(10);

int[] results = await Task.WhenAll(task1, task2);
Console.WriteLine($"Results: {results[0]}, {results[1]}");

Console.WriteLine("All operations completed!");

static async Task<string> FetchDataAsync()
{
    // Simulate async operation
    await Task.Delay(500);
    return "Data fetched successfully";
}

static async Task<int> CalculateAsync(int value)
{
    await Task.Delay(200);
    return value * 2;
}

Working with HttpClient

C# Example Code
static async Task<string> GetWebContentAsync(string url)
{
    using HttpClient client = new HttpClient();
    string content = await client.GetStringAsync(url);
    return content;
}

Async with try/catch

Exceptions from awaited tasks propagate normally and can be caught with standard try/catch blocks.

C# Example Code
static async Task<string> FetchSafelyAsync(string url)
{
    try
    {
        using HttpClient client = new HttpClient();
        client.Timeout = TimeSpan.FromSeconds(10);
        return await client.GetStringAsync(url);
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"Network error: {ex.Message}");
        return string.Empty;
    }
    catch (TaskCanceledException)
    {
        Console.WriteLine("Request timed out");
        return string.Empty;
    }
}

// Task.WhenAll — the first exception is re-thrown; wrap in try/catch as normal
async Task RunMultipleAsync()
{
    try
    {
        await Task.WhenAll(
            Task.FromException(new InvalidOperationException("Task 1 failed")),
            Task.Delay(100)
        );
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine($"Caught: {ex.Message}"); // Caught: Task 1 failed
    }
}

async void — The Anti-Pattern

async void methods cannot be awaited, and any exception they throw will crash the application. Only use async void for event handlers.

C# Example Code
// BAD — exceptions crash the app, caller cannot await or catch
async void LoadDataBad()
{
    await Task.Delay(1000);
    throw new Exception("This exception cannot be caught by the caller!");
}

// GOOD — async Task lets callers await and catch exceptions
async Task LoadDataGoodAsync()
{
    await Task.Delay(1000);
}

// ACCEPTABLE — async void only in event handlers
button.Click += async (sender, e) =>
{
    await LoadDataGoodAsync(); // properly awaited inside the void handler
};

ConfigureAwait(false)

By default, await captures the current synchronization context and resumes on it. In library code, use ConfigureAwait(false) to skip this overhead and avoid potential deadlocks.

C# Example Code
// Library code — ConfigureAwait(false) continues on a thread pool thread
static async Task<string> LibraryMethodAsync()
{
    await Task.Delay(100).ConfigureAwait(false);
    // Continues on a thread pool thread, not the original context
    return "result";
}

static async Task<byte[]> DownloadAsync(string url)
{
    using HttpClient client = new HttpClient();
    // Each await in library code should use ConfigureAwait(false)
    byte[] data = await client.GetByteArrayAsync(url).ConfigureAwait(false);
    return data;
}

// ASP.NET Core and console apps have no synchronization context,
// so ConfigureAwait(false) is optional but not harmful there.

Task vs Task<T> vs ValueTask<T>

C# Example Code
// Task — async operation with no return value
async Task SendEmailAsync(string to, string body)
{
    await Task.Delay(100); // simulate sending
    Console.WriteLine($"Email sent to {to}");
}

// Task<T> — async operation that returns a value
async Task<int> CountUsersAsync()
{
    await Task.Delay(100); // simulate DB query
    return 42;
}

// ValueTask<T> — prefer when the result is often available synchronously.
// Avoids a heap allocation on the fast path compared to Task<T>.
private int _cachedValue = -1;

async ValueTask<int> GetCachedValueAsync()
{
    if (_cachedValue != -1)
        return _cachedValue; // synchronous — no Task object allocated

    await Task.Delay(100);   // async path allocates only when needed
    _cachedValue = 99;
    return _cachedValue;
}

Common Deadlock Scenario to Avoid

Calling .Result or .Wait() on an async method from a thread that owns a synchronization context (WinForms, WPF, classic ASP.NET) blocks the thread while its continuation waits for the same thread — a deadlock.

C# Example Code
// DEADLOCK in UI / classic ASP.NET — never block on async code
string bad1 = FetchDataAsync().Result; // deadlocks!
FetchDataAsync().Wait();               // deadlocks!

// CORRECT — always await
string good = await FetchDataAsync();

// If you truly must call async code synchronously (avoid when possible),
// offload to the thread pool to escape the synchronization context:
string fallback = Task.Run(() => FetchDataAsync()).GetAwaiter().GetResult();

static async Task<string> FetchDataAsync()
{
    await Task.Delay(500);
    return "data";
}

Take It Further

Concurrency in C# Cookbook book cover

Struggling with async/await? The definitive recipe book for every concurrency pattern in .NET.

Concurrency in C# Cookbook — 2nd Edition · Stephen Cleary

Get it on Amazon →

As an Amazon Associate I earn from qualifying purchases.