MSDN Community

How to handle asynchronous operations in C# with async/await?

JS
John Smith
2 days ago
C# Async/Await Threading .NET

I'm working on a .NET Core application and need to perform several I/O bound operations. I've been reading about the async and await keywords and how they simplify asynchronous programming. However, I'm a bit confused about the best practices and common pitfalls.

Specifically, I'm wondering:

  • When should I use async and await?
  • How do I correctly handle exceptions in asynchronous methods?
  • What is the difference between ConfigureAwait(true) and ConfigureAwait(false)?
  • Are there any performance considerations I should be aware of?

Here's a basic example of what I'm trying to achieve:


public async Task<string> GetDataFromApiAsync(string url)
{
    using (var httpClient = new HttpClient())
    {
        var response = await httpClient.GetStringAsync(url);
        return response;
    }
}

public async Task ProcessDataAsync()
{
    var data = await GetDataFromApiAsync("https://api.example.com/data");
    // Process the data...
    Console.WriteLine("Data processed.");
}
                    

Any guidance or examples would be greatly appreciated!

3 Answers

42

Great question! The async and await pattern in C# is indeed powerful for managing I/O-bound or long-running operations without blocking the calling thread, typically the UI thread.

When to use async/await:

  • I/O-bound operations: Network requests (like your API call), database queries, file operations. These operations spend most of their time waiting for external resources.
  • CPU-bound operations (with caution): For very long-running CPU-bound tasks, consider using Task.Run() to offload them to a thread pool thread, and then await the result. Direct async on a CPU-bound operation without offloading can still tie up the thread it runs on.

Exception Handling:

Exceptions thrown within an awaited task are re-thrown when the await completes. You can catch them using standard try-catch blocks:


try
{
    var data = await GetDataFromApiAsync("https://api.example.com/data");
    // Process data
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"API request failed: {ex.Message}");
}
catch (Exception ex) // Catch other potential exceptions
{
    Console.WriteLine($"An error occurred: {ex.Message}");
}
                            

ConfigureAwait(false):

This is crucial for library code or any code that doesn't need to resume on the original synchronization context (e.g., UI thread). By using ConfigureAwait(false), you avoid capturing and marshaling back to the original context, which can prevent deadlocks and improve performance by allowing the thread pool to be reused more effectively.

In your application code (like ProcessDataAsync), ConfigureAwait(true) (which is the default) is often fine, especially if you need to update the UI. In library code, always prefer ConfigureAwait(false).


public async Task<string> GetDataFromApiAsync(string url)
{
    using (var httpClient = new HttpClient())
    {
        // Crucial for library code or performance-sensitive scenarios
        var response = await httpClient.GetStringAsync(url).ConfigureAwait(false);
        return response;
    }
}
                            

Performance Considerations:

  • Minimize the amount of work done before the first await if you need to resume on the original context.
  • Avoid unnecessary async void methods (except for event handlers). Use async Task or async Task<T>.
  • Use ConfigureAwait(false) judiciously, especially in libraries.
AP
Alice Brown
2 days ago Link Report
15

To add to Alice's excellent explanation, remember that async methods return Task or Task<T>. This allows the caller to also await the completion of your asynchronous operation.

If you have multiple independent asynchronous operations, you can start them concurrently and then await them all:


public async Task FetchMultipleApisAsync()
{
    var task1 = GetDataFromApiAsync("https://api.example.com/data1");
    var task2 = GetDataFromApiAsync("https://api.example.com/data2");

    // Wait for both tasks to complete concurrently
    await Task.WhenAll(task1, task2);

    Console.WriteLine("All data fetched.");
    // Process data from task1.Result and task2.Result
}
                            

This is much more efficient than awaiting them sequentially.

BW
Bob White
1 day ago Link Report
8

Just a quick note on async void. While generally discouraged, it's the standard for event handlers (like button clicks in UI frameworks) because the framework expects a void return type. If you make an event handler async Task, the framework won't know when it completes, leading to potential issues.

For event handlers, ensure your exception handling is robust as there's no task to await to catch exceptions.

CB
Chris Black
1 day ago Link Report

Your Answer