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, 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 return Task, Task<T>, or void.
  • await can only be used inside async 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 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”. 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.");
}