Asynchronous programming, or async dev, allows an application to execute multiple operations without blocking the execution thread.
In this tutorial, we will learn about async programming in C# by understanding how to work with the “async” and “await” keywords.
Understanding the Async Await
In C#, we use the “async” and “await” keywords as modifiers. Let us explore how each keyword works.
Async modifies a method, a lambda expression, or any anonymous method to run asynchronously.
Depending on the implementation, the async methods return a Task<T> or void. It is good to avoid returning a void from an async method unless in the case of event handlers.
Next, we have the “await” operator. This operator is applied to a task in an “async” method to suspend the execution of the method until the task that we are awaiting is completed. As you can guess, the method that contains the “await” operator must run asynchronously or have the async modifier.
Basic Usage
To better understand how this method works, let us look at a basic implementation of the “async” and “await” modifiers.
Consider a basic example as shown in the following:
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Starting");
await PrintHelloWorldAsync();
Console.WriteLine("Finished");
}
static async Task PrintHelloWorldAsync()
{
await Task.Delay(1000); // a task that takes long to complete
Console.WriteLine("Hello, world!");
}
}
The previous code provides an unequivocal and simple demonstration of using the “async” and “await” keywords for async programming.
We start by writing a basic string to the console. We then call the PrintHelloWorldAsync() method.
This method uses the “await” keyword to simulate an async task that takes a long time to complete. In this case, the task waits for one second. This can be any long-running task such as fetching the data from the network, API calls, etc.
Once we invoke the print method, we return the control to the “main” method which waits for the method to complete.
This should complete the task and print the string to the console. Finally, we return the control to the “main” method which prints the “Finished” string, indicating the program’s end.
Using “async” and “await”, we ensure that the main thread is free and not blocked during the task that is defined in the method.
Exception Handling
Like all development tasks, you are bound to encounter exceptions when executing the “async” tasks. It is, therefore, suitable to learn how to handle them if they occur in an “async” method.
Luckily, we can use basic try-and-catch blocks as we do when writing a synchronous code. An example demonstration is as follows:
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Starting");
await ProcessDataAsync();
Console.WriteLine("Finished");
}
static async Task ProcessDataAsync()
{
try
{
await LoadDataAsync();
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
static async Task LoadDataAsync()
{
await Task.Delay(1000); // long operation
throw new Exception("Error loading data.");
}
}
In this case, we perform the same operation by calling the ProcessDataAsync() method inside the “main” method. This, in turn, calls the LoadDataAsync() method which simulates a long-running operation. This throws an exception with an error message.
We then catch this exception in the catch block of the ProcessDataAsync method which returns the error message to the console.
Using the Task.WhenAll
C# also allows us to run multiple tasks in parallel and then await all of them using the “Task.WhenAll”.
Take a look at the following example code:
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Starting");
Task task1 = Task1();
Task task2 = Task2();
Task task3 = Task3();
await Task.WhenAll(task1, task2, task3);
Console.WriteLine("All Tasks Completed");
}
static async Task Task1()
{
await Task.Delay(1000);
Console.WriteLine("Task 1 Completed");
}
static async Task Task2()
{
await Task.Delay(2000);
Console.WriteLine("Task 2 Completed");
}
static async Task Task3()
{
await Task.Delay(1500);
Console.WriteLine("Task 3 Completed");
}
}
Let us examine the previous code in simple steps.
The first step is printing a basic message to denote a program execution.
We then define three main tasks and start their execution by their respective async method. The first task waits for 1 second, the second for two, and the third waits for 1.5. Each task prints a message to the console upon completion.
Note: We use the Task.WhenAll() method to await the completion of all three tasks before proceeding. This ensures that the program does not terminate prematurely until all the tasks are completed.
Dealing with Deadlocks
Like any case of async programming, it is good to learn how to handle the deadlocks in the program. C# allows us to use the ConfigureAwait(false) method to avoid the deadlocks when we don’t need the context to resume on the original context.
A basic snippet is as follows:
{
await Task.Delay(1000);
return "Data";
}
static async Task ProcessDataAsync()
{
string data = await GetDataAsync().ConfigureAwait(false);
Console.WriteLine(data);
}
Conclusion
This tutorial explored the most essential part of writing the async programs using the C# “async” and “await” keywords. You can reference the documentation for a more complex implementation and best practices.