Choose await/async over Task Alone

Choose await/async over Task Alone

Introduction

In C#, async/await and Task serve different purposes when it comes to asynchronous programming. async/await is a language feature that simplifies asynchronous code, while Task represents a unit of work that can be executed asynchronously. You don’t typically choose async/await over Task because they work together. Instead, you choose between using async/await with Task or using Task without async/await based on your programming goals and requirements.

Why you might prefer async/await over using Task alone:

Readability and Maintainability: async/await makes asynchronous code more readable and maintainable. It allows you to write asynchronous code that looks similar to synchronous code, making it easier to understand and debug. With async/await, you can write linear, sequential code that represents the natural flow of your program.

// Asynchronous code with async/await
async Task<int> CalculateSumAsync()
{
    int result1 = await GetNumber1Async();
    int result2 = await GetNumber2Async();
    return result1 + result2;
}

Synchronisation Context: async/await captures the current synchronisation context by default. This is essential for applications with a user interface (e.g., WPF, WinForms, ASP.NET) because it ensures that asynchronous operations can update the UI seamlessly without causing cross-threading issues. In contrast, using Task without async/await might require explicit synchronisation context management.

Exception Handling: async/await simplifies exception handling. Exceptions thrown in an async method can be caught with a regular try…catch block inside the method, which makes error handling more natural and straightforward.

async Task DoSomethingAsync()
{
    try
    {
        await SomeAsyncOperation();
    }
    catch (Exception ex)
    {
        // Handle exception
    }
}

Seamless Composition: async/await allows for the seamless composition of asynchronous operations. You can call asynchronous methods one after the other in a clear, sequential manner. This promotes code reusability and modularity.

async Task<int> CalculateTotalAsync()
{
    int result1 = await GetFirstValueAsync();
    int result2 = await GetSecondValueAsync();
    return result1 + result2;
}

Debugging: Debugging asynchronous code with async/await is more straightforward. The debugger can follow the logical flow of your code, making it easier to identify issues and inspect variables.

Task-Based APIs: Many modern libraries and APIs are designed with async/await in mind and expose methods that return Task or Task<T>. Using async/await allows you to leverage these APIs more naturally.

Conclusion

async/await is a powerful feature in C# that simplifies asynchronous programming, improves code readability, and makes it easier to handle exceptions and maintain UI responsiveness in applications.

While Task is essential for representing asynchronous operations, it is typically used in conjunction with async/await to achieve these benefits.

It’s important to understand when and how to use async/await appropriately to write clean, maintainable, and efficient asynchronous code.

The official documentation for await/async can be found here: – https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/

I have written other posts in the same vein here: – Asynchronous Task.Delay, Understanding Task.Yield

Stephen

Hi, my name is Stephen Finchett. I have been a software engineer for over 30 years and worked on complex, business critical, multi-user systems for all of my career. For the last 15 years, I have been concentrating on web based solutions using the Microsoft Stack including ASP.Net, C#, TypeScript, SQL Server and running everything at scale within Kubernetes.