Search code examples
c#asynchronousasync-awaittask-parallel-library

What is the difference between a loop and a Task.WhenAll?


I have a method inside which there is a foreach loop, inside the loop I call several asynchronous operations. I asked ChatGPT to do a review, and he said that you can improve performance by adding tasks to the list and calling Task.WhenAll().

foreach (var name in names)
{
    var some = await SomeMethodAsync1(name);
    await SomeMethodAsync2(name);
    //...
}

Redo to:

var tasks = names.Select(async n =>
{
    var some = await SomeMethodAsync1(n);
    await SomeMethodAsync2(n);
    //...
}).ToList();
await tasks.WhenAll();

And I don't understand why this is more productive? After all, I also call asynchronous methods with await inside Select, then what is the difference from a simple loop?


Solution

  • foreach loop awaits at each iteration. So each iteration starts after previous is done and all calls to SomeMethodAsync1 and SomeMethodAsync2 will be done separately (consecutive) for each iteration.

    When you do Select, it starts the task that represent async lambda that you have defined and each item in array is processed in parallel now.

    Now having all tasks representing the parallel work, you can use Task.WhenAll() method to await all tasks (btw. it cannot be called as you shown, as far as I know, WhenAll is static method on Task class).

    Here's simple example:

    Console.WriteLine("NoParallel");
    await NoParallel();
    
    Console.WriteLine("Parallel");
    await Parallel();
    
    async Task NoParallel()
    {
        var items = Enumerable.Range(0, 10);
        foreach (var item in items)
        {
            await AsyncMethod();
        }
    }
    
    async Task Parallel()
    {
        var items = Enumerable.Range(0, 10);
        await Task.WhenAll(items.Select(x => AsyncMethod()));
    }
    
    async Task AsyncMethod()
    {
        Console.WriteLine("Start async method");
        await Task.Delay(50);
        Console.WriteLine("End async method");
    }
    

    Which would print (indicating what is executed in parallel and what not):

    NoParallel
    Start async method
    End async method
    Start async method
    End async method
    Start async method
    End async method
    Start async method
    End async method
    Start async method
    End async method
    Start async method
    End async method
    Start async method
    End async method
    Start async method
    End async method
    Start async method
    End async method
    Start async method
    End async method
    Parallel
    Start async method
    Start async method
    Start async method
    Start async method
    Start async method
    Start async method
    Start async method
    Start async method
    Start async method
    Start async method
    End async method
    End async method
    End async method
    End async method
    End async method
    End async method
    End async method
    End async method
    End async method
    End async method