In C#, async
and await
are keywords that simplify the syntax when working with asynchronous tasks, also making it easier to read.
- async: 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 (that is, it runs asynchronously).
On the other hand, an async
method can contain one or more operations marked with await
. This means that the method will “wait” for this operation to finish before continuing.
Once the “awaited” operation completes, the async
method resumes execution at the point where it left off.
It’s important to remember that:
async
methods must returnTask
,Task<T>
, orvoid
.await
can only be used insideasync
methods.
It is a common mistake to think that all async methods must be awaited. It is perfectly valid to want to launch an async method, and have it run 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-blockingly.
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); // Simulate a 3-second process
// Rest of the code
Console.WriteLine("Operation completed.");
}
This means that when we invoke this function, it will execute non-blockingly (in parallel).
Now suppose we want to call that function. We could do it like this.
static async Task Main()
{
Console.WriteLine("Starting Main...");
// Calls 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 has calledLongOperation
Main
has waited forLongOperation
to finishMain
has completed
The “chain” of asyncs
One curious thing about async and await is that they “chain”. The method you mark as async must be called by an async, which must be called by…
That is, once you start using async methods, everything that uses it will have 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 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
// Waits until both tasks are complete
await Task.WhenAll(task1, task2);
Console.WriteLine("All tasks have completed.");
}