In C#, async
and await
are keywords that simplify the syntax when working with asynchronous tasks, making it easier to read.
- async: It is placed before a method to indicate that it contains asynchronous operations.
- await: Pauses execution until the asynchronous task is complete and allows the thread to execute other tasks in the meantime.
How do async
and await
work?
When a method is marked as async
, it means that this method does not block the thread that executes it (i.e., it runs asynchronously).
On the other hand, an async
method can contain one or more operations marked with await
. This means that the method is going to “wait” for this operation to finish before continuing.
Once the “awaited” operation is complete, the async
method resumes execution at the point where it left off.
It is important to remember that:
async
methods must returnTask
,Task<T>
, orvoid
.await
can only be used withinasync
methods.
It is a common mistake to think that all async methods need to be awaited. It is perfectly valid to want to launch an async method and have it execute in parallel.
Example of async and await
Let’s see it with an example. Suppose we have a LongOperation
that we want to execute non-blocking.
First, we will mark the method as asynchronous using the async
keyword.
// Asynchronous method that simulates a long-running operation
static async Task LongOperation()
{
Console.WriteLine("Starting long-running operation...");
await Task.Delay(3000); // Simulating a 3-second process
// Rest of the code
Console.WriteLine("Operation completed.");
}
This means that when we invoke this function, it will execute non-blocking (in parallel).
Now suppose we want to call that function. We could do it like this.
static async Task Main()
{
Console.WriteLine("Starting Main...");
// Call an asynchronous method
await LongOperation();
Console.WriteLine("End of main.");
}
If we run this, we will get the result
Starting Main...
Starting long-running operation...
# (a 3s pause).
Operation completed.
End of main.
That is,
- The
Main
function has started and calledLongOperation
Main
has waited forLongOperation
to finishMain
has completed
The “chain” of asyncs
One curious thing about async and await is that they “chain” together. The method you mark as async must be invoked by an async, which must be invoked by…
In other words, once you start using async methods, everything that uses it has to be async (up to the main Main
method). At some point, you will have to “break the chain”.
To do this, remember that you can use Task.Wait();
. For example, this version of the previous Main
is not asynchronous.
static void SynchronousMain()
{
Console.WriteLine("Starting Main...");
LongOperation.Wait();
Console.WriteLine("End of main.");
}
Combining multiple asynchronous tasks
In some cases, it is necessary to run multiple tasks asynchronously. This can be done using Task.WhenAll
or Task.WhenAny
.
- Task.WhenAll: Runs multiple tasks concurrently and waits until all are complete.
- Task.WhenAny: Runs multiple tasks and returns when any of them is complete.
static async Task RunTasksConcurrently()
{
Task task1 = Task.Delay(2000); // Simulates a 2-second task
Task task2 = Task.Delay(3000); // Simulates a 3-second task
// Wait until both tasks are complete
await Task.WhenAll(task1, task2);
Console.WriteLine("All tasks have completed.");
}