C# Async Await

By FoxLearn 1/9/2025 6:53:37 AM   140
This C# async/await tutorial explains how to use the async and await keywords to write asynchronous code in C#.

What is async and await in C#?

The async keyword transforms a method into an asynchronous method, enabling the use of the await keyword within its body. When await is applied to a task, it suspends the method's execution and returns control to the caller until the task completes. Note that await can only be used inside methods marked as async.

Asynchronous programming allows tasks to run concurrently with the main program, improving efficiency. In C#, the async and await keywords simplify this process, making it easier to write non-blocking code. C# has a built-in asynchronous programming model that supports concurrent task execution.

Concurrent programming is used for two types of tasks: I/O-bound tasks and CPU-bound tasks. I/O-bound tasks involve operations like network requests, database access, or file reading/writing, which depend on external resources. CPU-bound tasks, on the other hand, are computationally intensive tasks, such as mathematical calculations or graphics processing.

The async modifier is applied to methods, lambda expressions, or anonymous methods to create asynchronous methods. An async method runs synchronously until it hits the first await operator, at which point it suspends execution and returns control to the caller while the awaited task completes. Once the task finishes, the await operator resumes the method and returns any result.

If an async method doesn't include an await operator, it executes synchronously. In C#, a Task represents a concurrent operation.

C# Async Await Task

For example, using async and await in C# to perform an asynchronous task, such as fetching data from a simulated web service:

// Simulate an asynchronous task
// c# async await task example with return value
static async Task<string> FetchDataAsync()
{
    // Simulate a delay using Task.Delay (representing I/O-bound operation like network access)
    await Task.Delay(2000); // Wait for 2 seconds
    return "c# async await example"; // Return data after the delay
}

This method is marked with the async keyword and returns a Task<string>. It simulates an asynchronous operation by waiting for 2 seconds using Task.Delay().

The Main method is also marked as async, allowing the use of await directly in the entry point.

// async task in c#
static async Task Main(string[] args)
{
    Console.WriteLine("Fetching data...");
    // Call the asynchronous method and await its result
    var result = await FetchDataAsync();
    Console.WriteLine($"Data fetched: {result}");
}

It calls FetchDataAsync() and waits for the result using await.

Output:

Fetching data...
Data fetched: c# async await example

Task.WhenAll is a powerful method in C# that allows you to run multiple asynchronous tasks concurrently and await their completion. It takes an array or a collection of Task objects and returns a single Task that completes when all the tasks in the collection have finished.

We create a Stopwatch instance (sw) at the beginning of the Main method. This is used to measure the time it takes to complete all asynchronous tasks.

// async task in c#
static async Task Main(string[] args)
{
    var sw = new Stopwatch();
    sw.Start();
    // Create multiple tasks
    Task task1 = DoWorkAsync(1);
    Task task2 = DoWorkAsync(2);
    Task task3 = DoWorkAsync(3);
    // Waiting for all tasks to complete concurrently
    await Task.WhenAll(task1, task2, task3);
    Console.WriteLine("All tasks finished.");
    // Stop the stopwatch and output the elapsed time
    sw.Stop();
    var elapsed = sw.ElapsedMilliseconds;
    Console.WriteLine($"elapsed: {elapsed} ms");
}

The stopwatch is started with sw.Start() and stopped at the end with sw.Stop(). The elapsed time is displayed in milliseconds.

The DoWorkAsync method simulates a delay of 2000 milliseconds (2 seconds) using Task.Delay(2000). This method returns a Task, making it asynchronous.

// async task in c#
static async Task DoWorkAsync(int taskId)
{
    Console.WriteLine($"Task {taskId} is starting.");
    await Task.Delay(2000);  // Simulate work with a 2-second delay
    Console.WriteLine($"Task {taskId} is complete.");
}

We invoke DoWorkAsync three times to simulate three independent tasks that take time to complete.

Instead of waiting for each task to complete sequentially, we run them concurrently. This is done by creating Task objects (task1, task2, task3) for each asynchronous operation.

The await Task.WhenAll(task1, task2, task3) statement allows the program to wait until all three tasks finish, without blocking the main thread during their execution.

Task.WaitAll is a synchronous method used to block the calling thread until all the provided tasks have completed. It ensures that the caller waits for all the tasks to finish before continuing.

// async task in c#
async Task DoWorkAsync(int taskId, int delay)
{
    await Task.Delay(delay);
    Console.WriteLine($"Task {taskId} is complete.");
}

You need to call DoWorkAsync(1, 4000), DoWorkAsync(2, 5000), and DoWorkAsync(3, 1000) and then pass their Task objects into Task.WaitAll.

// Start the tasks and pass the Task objects to WaitAll
Task.WaitAll(DoWorkAsync(1, 4000), DoWorkAsync(2, 5000), DoWorkAsync(3, 1000));

If you're working with asynchronous code, you might prefer Task.WhenAll instead. WhenAll returns a Task that you can await to ensure all tasks complete. Unlike WaitAll, WhenAll does not block the thread, and you can use await to avoid blocking the calling thread.

await Task.WhenAll(DoWorkAsync(1, 4000), DoWorkAsync(2, 5000), DoWorkAsync(3, 1000));

Since each task runs concurrently, they do not block one another. Instead of waiting for each task to complete sequentially, they execute at the same time. This reduces the total execution time, especially when dealing with I/O-bound tasks (like network requests, file reads, or database queries).