Language: EN

csharp-async-await

Async and await in C#

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 return Task, Task<T>, or void.
  • await can only be used within async 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 called LongOperation
  • Main has waited for LongOperation to finish
  • Main 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.");
}