C# Async Programming

Learn how to write efficient, responsive applications with C# asynchronous programming

Back to Dashboard

What is Async Programming?

Async programming is a way to write code that doesn't block the main thread while waiting for time-consuming operations to complete.

Real-life Analogy

Imagine ordering food at a restaurant. Synchronous programming is like waiting at the counter for your food, unable to do anything else. Async programming is like getting a buzzer - you can sit down, check your phone, or chat with friends while waiting for your food to be ready.

Why We Need It:

  • Keeps UI responsive
  • Improves app performance
  • Handles multiple operations simultaneously
  • Better utilization of system resources

Basic Async/Await Pattern

public async Task<string> DownloadDataAsync()
{
    using HttpClient client = new HttpClient();
    // This doesn't block the thread while waiting
    string result = await client.GetStringAsync("https://api.example.com/data");
    return result;
}

// Calling the async method
async Task ProcessData()
{
    string data = await DownloadDataAsync();
    Console.WriteLine(data);
}

Async vs Synchronous

Synchronous Asynchronous
Blocks the current thread Doesn't block the current thread
Simple to understand More complex to implement
Inefficient for I/O operations Efficient for I/O operations
Can cause UI to freeze Keeps UI responsive
Easier error handling Requires careful error handling

When to Use Async:

  • Network requests (HTTP API calls)
  • File I/O operations
  • Database queries
  • Any operation that involves waiting

Async Flow Explained

1
Async method is called
2
Method executes until await
3
Control returns to caller
4
Awaitable operation runs in background
5
When operation completes, method resumes

Async Method Structure

// Async method signature
public async Task<ReturnType> MethodNameAsync()
{
    // Synchronous part
    Console.WriteLine("Starting async operation");
    
    // Asynchronous part - doesn't block
    var result = await SomeAsyncOperation();
    
    // Continuation after await
    Console.WriteLine($"Operation completed: {result}");
    
    return result;
}

Error Handling in Async Code

Async methods use the same try-catch pattern as synchronous code, but with some important considerations.

async Task ProcessDataAsync()
{
    try
    {
        string data = await DownloadDataAsync();
        Console.WriteLine(data);
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"Network error: {ex.Message}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Unexpected error: {ex.Message}");
    }
}

// Async void methods should be avoided except for event handlers
async void ButtonClick(object sender, EventArgs e)
{
    try
    {
        await ProcessDataAsync();
    }
    catch (Exception ex)
    {
        // Handle error in UI context
        Console.WriteLine($"Error: {ex.Message}");
    }
}

Important:

Always handle exceptions in async methods. Unhandled exceptions in async void methods can crash your application.

Best Practices

Do

  • Use async all the way through call chain
  • Use ConfigureAwait(false) in library code
  • Use CancellationToken for cancelable operations
  • Use ValueTask for performance-critical code
  • Name async methods with "Async" suffix

Don't

  • Use async void (except event handlers)
  • Block on async code with .Result or .Wait()
  • Ignore returned Task objects
  • Use async for CPU-bound operations
  • Forget to handle exceptions

Example of Good Async Code

public class DataService
{
    private readonly HttpClient _httpClient;
    
    public DataService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    
    // Good: Proper async method with cancellation support
    public async Task<string> GetDataAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            // Use ConfigureAwait false in library code
            return await _httpClient.GetStringAsync("https://api.example.com/data", cancellationToken)
                .ConfigureAwait(false);
        }
        catch (TaskCanceledException)
        {
            Console.WriteLine("Request was canceled");
            return string.Empty;
        }
    }
}

// Good: Proper async consumption
public class DataProcessor
{
    private readonly DataService _dataService;
    
    public async Task ProcessDataAsync()
    {
        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        
        string data = await _dataService.GetDataAsync(cts.Token);
        Console.WriteLine($"Received data: {data}");
    }
}