How to run multiple async tasks and waiting for them all to complete in C#?

In C#, there are several ways to run multiple asynchronous tasks and wait for them all to complete. The two main approaches are Task.WaitAll (synchronous blocking) and Task.WhenAll (asynchronous non-blocking).

Task.WaitAll blocks the current thread until all tasks have completed execution, while Task.WhenAll returns a task that completes when all provided tasks have finished, without blocking the calling thread.

Syntax

Following is the syntax for using Task.WaitAll

Task.WaitAll(task1, task2, task3);

Following is the syntax for using Task.WhenAll

await Task.WhenAll(task1, task2, task3);

Using Task.WhenAll (Non-blocking)

The Task.WhenAll method creates a task that will complete when all the provided tasks have completed. It does not block the current thread −

using System;
using System.Threading.Tasks;

class Program {
    static async Task Main(string[] args) {
        Task task1 = Task.Run(async () => {
            for (var i = 0; i < 3; i++) {
                Console.WriteLine("Task 1 - iteration {0}", i);
                await Task.Delay(1000);
            }
            Console.WriteLine("Task 1 complete");
        });

        Task task2 = Task.Run(async () => {
            for (var i = 0; i < 3; i++) {
                Console.WriteLine("Task 2 - iteration {0}", i);
                await Task.Delay(1500);
            }
            Console.WriteLine("Task 2 complete");
        });

        Console.WriteLine("Starting tasks...");
        await Task.WhenAll(task1, task2);
        Console.WriteLine("Both Tasks Completed.");
    }
}

The output of the above code is −

Starting tasks...
Task 1 - iteration 0
Task 2 - iteration 0
Task 1 - iteration 1
Task 1 - iteration 2
Task 1 complete
Task 2 - iteration 1
Task 2 - iteration 2
Task 2 complete
Both Tasks Completed.

Using Task.WaitAll (Blocking)

The Task.WaitAll method blocks the current thread until all tasks have completed execution −

using System;
using System.Threading.Tasks;

class Program {
    static void Main(string[] args) {
        Task task1 = Task.Run(async () => {
            for (var i = 0; i < 3; i++) {
                Console.WriteLine("Task 1 - iteration {0}", i);
                await Task.Delay(1000);
            }
            Console.WriteLine("Task 1 complete");
        });

        Task task2 = Task.Run(async () => {
            for (var i = 0; i < 3; i++) {
                Console.WriteLine("Task 2 - iteration {0}", i);
                await Task.Delay(1500);
            }
            Console.WriteLine("Task 2 complete");
        });

        Console.WriteLine("Waiting for tasks to complete...");
        Task.WaitAll(task1, task2);
        Console.WriteLine("Both Tasks Completed.");
    }
}

The output of the above code is −

Waiting for tasks to complete...
Task 1 - iteration 0
Task 2 - iteration 0
Task 1 - iteration 1
Task 1 - iteration 2
Task 1 complete
Task 2 - iteration 1
Task 2 - iteration 2
Task 2 complete
Both Tasks Completed.

Handling Task Results with Task.WhenAll

When working with tasks that return values, Task.WhenAll can collect all results −

using System;
using System.Threading.Tasks;

class Program {
    static async Task Main(string[] args) {
        Task<int> task1 = Task.Run(async () => {
            await Task.Delay(1000);
            Console.WriteLine("Task 1 completed");
            return 10;
        });

        Task<int> task2 = Task.Run(async () => {
            await Task.Delay(1500);
            Console.WriteLine("Task 2 completed");
            return 20;
        });

        Task<int> task3 = Task.Run(async () => {
            await Task.Delay(500);
            Console.WriteLine("Task 3 completed");
            return 30;
        });

        int[] results = await Task.WhenAll(task1, task2, task3);
        Console.WriteLine("All tasks completed. Results: [{0}]", string.Join(", ", results));
    }
}

The output of the above code is −

Task 3 completed
Task 1 completed
Task 2 completed
All tasks completed. Results: [10, 20, 30]

Comparison

Task.WaitAll Task.WhenAll
Blocks the calling thread Does not block the calling thread
Synchronous operation Asynchronous operation
Cannot be used with await Can be used with await
No return value Returns results from all tasks

Conclusion

Use Task.WhenAll with await for asynchronous programming as it doesn't block the calling thread and allows for better scalability. Use Task.WaitAll only when you need synchronous blocking behavior. Both methods ensure all tasks complete before proceeding.

Updated on: 2026-03-17T07:04:36+05:30

3K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements