The upcoming release of .NET 9 introduces the Task.WhenEach method, which enables developers to handle asynchronous tasks more elegantly. The Task.WhenEach method allows us to iterate through tasks as they complete, without having to call Task.WaitAny repeatedly in order to select the next task that completes. Task.WhenEach will be beneficial particularly in scenarios where you are waiting for tasks that differ in their execution times.

While the Task.WhenAll and Task.WhenAny methods also can be used to handle multiple tasks, they have certain limitations that Task.WhenEach was designed to solve. In this article, we’ll discuss how Task.WhenEach differs from Task.WhenAll and from Task.WhenAny and we’ll examine how we can make use of the Task.WhenEach method in .NET 9 using C# 13.

Create a console application project in Visual Studio 2022 Preview

First off, let’s create a .NET Core console application project in Visual Studio 2022 Preview. Assuming Visual Studio 2022 Preview is installed in your system, follow the steps outlined below to create a new .NET Core console application project.

  1. Launch the Visual Studio 2022 Preview IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Click Next.
  7. In the “Additional information” window shown next, choose “.NET 9.0 (Preview)” as the framework version you would like to use.
  8. Click Create.

This will create a new .NET 9 console application project in Visual Studio 2022 Preview. We’ll use this .NET 9 console application project to work with the code examples shown in the subsequent sections of this article.

Task.WhenAll, Task.WhenAny, and Task.WhenEach

Let us examine the problem that this new method Task.WhenEach is trying to solve. Until .NET 9, we’ve had two asynchronous methods in the Task class that we could use to execute multiple asynchronous tasks. These methods are Task.WhenAll and Task.WhenAny.

Task.WhenAll creates a task that will complete when all of a number of supplied tasks have completed, whereas Task.WhenAny creates a task that will complete when any of a number of supplied tasks have completed. In both cases, these Task objects can be supplied in an enumerable collection or an array. While both methods are useful, neither lets us respond to the completion of each task in turn. Enter the Task.WhenEach method.

Task.WhenEach is a new asynchronous static method that returns an IAsyncEnumerable, allowing us to iterate through each task as it completes. Hence, we can take advantage of an “await foreach” loop to process each task one at a time, thereby simplifying our source code and making it efficient. In essence, the Task.WhenEach method enables us to process each task as it becomes available without requiring us to wait for all tasks to complete before proceeding.

The Task.WhenEach method is defined in the Task class in the System.Threading.Tasks namespace:


namespace System.Threading.Tasks;
public class Task
{
   public static IAsyncEnumerable WhenEach(params Task[] tasks);
   public static IAsyncEnumerable WhenEach(params ReadOnlySpan tasks);
   public static IAsyncEnumerable WhenEach(IEnumerable tasks);
}

Task.WhenEach example in .NET 9

Let’s now examine how we might use the Task.WhenEach method in C#. Create the following method in the Program.cs file in the console application project we created earlier.


async Task GenerateRandomStringAsync()
{
    using Aes crypto = Aes.Create();
    crypto.GenerateKey();
    string? data = Convert.ToBase64String(crypto.Key);
    var delay = Random.Shared.Next(1000, 10000);
    await Task.Delay(delay);
    return data;
}

The async method above creates a random string using the AES (Advanced Encryption Standard) cryptographic algorithm and then returns it. The code snippet below shows how you can create new Task instances by invoking the GenerateRandomStringAsync method and storing those instances in a list of tasks.


var tasks = new List>(10);
for (var i = 0; i 

Now, let’s take advantage of the Task.WhenEach method and a foreach loop to display the strings at the console window as each task completes. Note the usage of the await keyword preceding the foreach statement.


await foreach (var data in Task.WhenEach(tasks))
{
    Console.WriteLine(await data);
}

When you execute the console application, the random strings will be displayed at the console window as shown in Figure 2.

Task.WhenEach method

Figure 2. The Task.WhenEach method in action. 

IDG

Note that you could use the Task.WhenAny method to achieve the same objective, but it would require you to write additional code that loops through the completed tasks as shown in the code snippet given below.


while (tasks.Any())
{
    Task completedTask = await Task.WhenAny(tasks);
    tasks.Remove(completedTask);
    string data = await completedTask;
    Console.WriteLine(data);
}

Complete source code of Task.WhenEach example in C# 13

The complete source code of the Program.cs file is given below for reference.


using System.Security.Cryptography;
var tasks = new List>(10);
for (var i = 0; i  GenerateRandomStringAsync()
{
    using Aes crypto = Aes.Create();
    crypto.GenerateKey();
    string? data = Convert.ToBase64String(crypto.Key);
    var delay = Random.Shared.Next(1000, 10000);
    await Task.Delay(delay);
    return data;
}

Add cancellation logic to the Task.WhenEach method

So far so good. But what if a task is cancelled? To handle cancellations, you can take advantage of the .WithCancellation extension method and check for cancellation within the foreach loop as shown in the code snippet given below.


using var tokenSource = new CancellationTokenSource(10_000);
var token = tokenSource.Token;
await foreach (var data in Task.WhenEach(tasks).WithCancellation(token))
{
    if (!tokenSource.TryReset()) 
        token.ThrowIfCancellationRequested();
    Console.WriteLine(await data);
    tokenSource.CancelAfter(10_000);
}

In the preceding code example, CancellationTokenSource is used to create instances of a CancellationToken, which represents a cancellation token to be used for cancellation of a task. The ThrowIfCancellationRquested method is called to throw an OperationCanceledException if cancellation has been requested. The CancelAfter method is used to schedule a cancel operation once the specified number of milliseconds elapses.

Key takeaways

The Task.WhenEach is a new asynchronous static method introduced in .NET 9 that addresses the limitations of the Task.WhenAll and Task.WhenAny methods. By enabling the immediate processing of completed tasks, it enhances the performance and scalability of your applications considerably.

Note that you can use ThrowIfCancellationRequested from within a task only. In this case, you will not have to handle any exception explicitly. Instead, when this method is called on a token instance, the execution leaves the currently running task and the Task.IsCancelled property is set to True.