async await in C#: 3 Beginner Tips You Need to Know
Tags: csharp, dotnet, beginners, coding
Published: 2024-03-06 17:00:00

The post [async await in C#: 3 Beginner Tips You Need to Know](https://www.devleader.ca/2024/02/27/async-await-in-c-3-beginner-tips-you-need-to-know/) appeared first on [Dev Leader](https://www.devleader.ca).

Async await in C# is a feature that allows you to write asynchronous code that is easy to read and maintain. It greatly simplifies how you handle asynchronous operations by providing a structured way to await tasks and their results. With the rate at which computers are scaling with respect to performance & cores, and our growing need to interface with many external services, async await in C# can make our developer lives easier.

In the following sections, I’ll highlight 3 beginner tips for using async await in C#. Each tip will be accompanied by a code example, allowing you to see these concepts in action. These [tips will cover important aspects such as error handling,](https://www.devleader.ca/2023/10/22/how-to-handle-exceptions-in-csharp-tips-and-tricks-for-streamlined-debugging/) cancellation, and performance optimization, so you’ll be off to the races writing async code.

---

## Tip 1: Understand the Basics of async await in CSharp

Async await in C# are two special keywords that are used to handle asynchronous code execution. Asynchronous programming enables us to write C# code that can perform multiple operations concurrently, improving the overall performance and responsiveness of an application. In C#, a task represents an asynchronous operation and it’s used to encapsulate a unit of work that can be executed [concurrently with other tasks](https://www.devleader.ca/2023/01/22/tasks-backgroundworkers-and-threads-simple-comparisons-for-concurrency/). When using async/await, you can use tasks to handle and manage asynchronous operations in a structured way.

To define an async method in C#, you need to add the async modifier to the method signature. For example:

```csharp
public async Task DoAsyncWork()
{
    // Asynchronous code here
}
```

In the above example, the method `DoAsyncWork` is declared as async and returns a `Task`. This indicates that the method will perform asynchronous operations and can be awaited.

Within the async method, you can use the await keyword to signal the point where the execution should wait for the completion of an asynchronous operation. For example:

```csharp
public async Task DoAsyncWork()
{
    // Perform asynchronous operations
    await Task.Delay(1000);

    // Continue with other operations
}
```

In the above example, the `await Task.Delay(1000)` statement pauses the execution of the method until the `Task.Delay` operation completes. This allows other code, such as other async tasks, to execute concurrently. By using tasks, you can [harness the power](https://www.devleader.ca/2024/01/31/custom-middleware-in-asp-net-core-how-to-harness-the-power/) of asynchronous programming in C#.

{% embed https://devleaderweekly.substack.com/embed %}

---

## Tip 2: Handling Multiple Exceptions with async await

[Exception handling is an important aspect of writing any software application](https://www.devleader.ca/2023/10/22/how-to-handle-exceptions-in-csharp-tips-and-tricks-for-streamlined-debugging/). When working with async code in C#, handling exceptions can present some challenges due to the asynchronous nature of the code. In certain situations, multiple exceptions can occur when executing async code. It’s important to be able to handle and aggregate these exceptions effectively. To handle multiple exceptions, C# provides the `AggregateException` class, which allows us to group and manage multiple exceptions as a single entity.

When multiple exceptions occur in an async operation, the `AggregateException` class collects these exceptions and allows us to access and handle them in a centralized manner. This can be particularly useful for scenarios where we need to process all exceptions generated during an async operation. To handle exceptions using the `AggregateException` class, we can wrap the code that may throw exceptions in a try-catch block. Within the catch block, we can access the `InnerExceptions` property of the `AggregateException` to access each individual exception that occurred.

Here’s an example of how to use the `AggregateException` class to handle and aggregate exceptions in async code:

```csharp
try
{
    await SomeAsyncOperation();
}
catch (AggregateException ex)
{
    foreach (var innerException in ex.InnerExceptions)
    {
        // Handle each individual exception
        Console.WriteLine($"Exception: {innerException.Message");
    }
}
```

In the example above, `SomeAsyncOperation` represents an async operation that may throw one or more exceptions. By wrapping the `await` statement in a try-catch block and catching an `AggregateException`, we are able to access and handle each individual exception that occurred within the async operation. By properly handling and aggregating exceptions in async code using the `AggregateException` class, we can ensure that our code can gracefully handle any unexpected errors that may occur during async operations.

---

## Tip 3: Avoiding Deadlocks with async await

Deadlocks can occur when two or more tasks are waiting for each other to complete indefinitely, resulting in a state where none of the tasks can proceed.

### Stop Blocking Your Async Calls!

One of the most common causes of deadlocks in async methods includes using the `Task.Result` or `Task.Wait` methods inside an async method, which blocks the main thread and can lead to deadlocks. The best advice for avoiding deadlocks with async code:

> “Don’t block on async code”
> 
> [Stephen Cleary](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html)

He points out in his article that this tip (stop blocking) or the tip that immediately follows this (configuring the awaiter) are both able to help prevent deadlocks… but relying ONLY on the next pattern is more of a risk. That is, of these two suggestions, making your C# code async await all of the way through is the safer bet.

That means instead of having something like:

```csharp
public class MyController(
  IWeatherService _weatherService) :
  ApiController
{
  public string GetWeatherData()
  {
    var weatherData = _weatherService.GetWeatherData(...).Result;
    return Results.OK(weatherData);
  }
}
```

Instead, we’d make the top-level async as well:

```csharp
public class MyController(
  IWeatherService _weatherService) :
  ApiController
{
  public async Task<string> GetWeatherData()
  {
    var weatherData = await _weatherService.GetWeatherData(...);
    return Results.OK(weatherData);
  }
}
```

As soon as we introduce a `.Result` or `.Wait()` call, we are effectively blocking — and that is one of the key situations that can lead to deadlocks as the application gets more complex.

### Using ConfigureAwait(false) with Async Await in CSharp

In order to prevent deadlocks, the `ConfigureAwait` method can be used with the `false` parameter. This instructs the `await` operator to not capture the current synchronization context, allowing the continuation to be executed on any available thread from the thread pool — not just the thread that started the call!

With `ConfigureAwait(false)`, we can explicitly state that we do not require the original synchronization context to be captured, preventing the deadlock issue. Keep in mind that using `ConfigureAwait(false)` should be done carefully and only when it is absolutely necessary, as it may introduce potential issues if the code relies on the captured synchronization context for specific requirements. For example, if you’re running UI logic and you come back to execution not on the UI thread, you may be in for some surprises!

Let’s take a look at an example to illustrate the usage of `ConfigureAwait(false)` to prevent deadlocks:

```csharp
public async Task<string> GetDataAsync()
{
    // NOTE: assume we enter this method on some given thread

    // use a false parameter on ConfigureAwait
    await SomeAsyncMethod().ConfigureAwait(false);

    // NOTE: we could be on a different thread than when we started this method!

    return ProcessData();
}
```

In the above example, `SomeAsyncMethod` is awaited using `ConfigureAwait(false)`. This allows the continuation of the code to execute on any available thread, ensuring that the synchronization context is not captured and preventing potential deadlocks.

And while Stephen Cleary does say that this will help prevent deadlocks, he also had this to add:

> Using `ConfigureAwait(false)` to avoid deadlocks is a dangerous practice. You would have to use `ConfigureAwait(false)` for *every*`await` in the transitive closure of all methods called by the blocking code, *including all third- and second-party code*. Using `ConfigureAwait(false)` to avoid deadlock is at best [just a hack](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx?WT.mc_id=DT-MVP-5000058)).
> 
> [Stephen Cleary](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html)

---

## Wrapping up Async Await in CSharp

In this article, I’ve discussed 3 beginner tips for using async await in C# to write concurrent code. Async await in C# can be a lot to absorb right in the beginning, so it will take some time and practice for it to feel more natural!

We got to see the basics of the keyword usage for async await — With this, you can now write asynchronous code! From there, we looked at how multiple exceptions can arise in async await code paths and how to navigate that. And we concluded with some ideas around preventing deadlocks by looking at 2 possibilities we can leverage.

Async await in C# is something you’re going to want to learn and practice! If you found this useful and you’re looking for more learning opportunities, consider [subscribing to my free weekly software engineering newsletter](https://subscribe.devleader.ca) and check out my [free videos on YouTube](https://www.youtube.com/@devleader)!

{% embed https://devleaderweekly.substack.com/embed %}

---

## **Want More Dev Leader Content?**

* Follow along on this platform if you haven’t already!    
* Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos:  
    [**SUBSCRIBE FOR FREE**](https://subscribe.devleader.ca/)    
* Looking for courses? Check out my offerings:  
    [**VIEW COURSES**](https://devleader.ca/courses)    
* E-Books & other resources:  
    [**VIEW RESOURCES**](https://products.devleader.ca/)    
* Watch hundreds of full-length videos on my YouTube channel:  
    [**VISIT CHANNEL**](https://youtube.com/@devleader?sub_confirmation=1)    
* Visit my website for hundreds of articles on various software engineering topics (including code snippets):  
    [**VISIT WEBSITE**](https://devleader.ca/)    
* Check out the repository with many code examples from my articles and videos on GitHub:  
    [**VIEW REPOSITORY**](https://github.com/ncosentino/DevLeader)