Uncertainty about the behavior of Task.WhenAny

96 Views Asked by At

Say task1, task2, task3 are tasks that I start at the beginning of the function, what does await Task.WhenAny(task1,task2,task3); return if more than one task has finished before the code reaches that await statement? Is it a random task? The first from the arguments that finished? A list of the tasks that have completed?

It does not matter by the way, I'm just wondering if it's uncertain behavior or if there's a priority

1

There are 1 best solutions below

0
Peter Csala On

Let's start with this super simple application:

TaskCompletionSource<object> tsc = new();
CancellationTokenSource cts = new(1_000);
cts.Token.Register(() => tsc.SetResult((object)null), useSynchronizationContext: false);
var task1 = tsc.Task;
var task2 = Task.Delay(1_000);
var task3 = Task.Run(async () => { await Task.Delay(1_000); });

var res = await Task.WhenAny(task1, task2, task3);

Console.WriteLine($"Task1: {task1.Id}");
Console.WriteLine($"Task2: {task2.Id}");
Console.WriteLine($"Task3: {task3.Id}");
Console.WriteLine($"res: {res.Id}");

Here we have three tasks and all will complete in a second. If you run this application multiple times then res.Id will be one of the three tasks depending the task scheduling.


Now, let's extend it with tasks that are completed before reaching the Task.WhenAny

TaskCompletionSource<object> tsc = new();
CancellationTokenSource cts = new(1_000);
cts.Token.Register(() => tsc.SetResult((object)null), useSynchronizationContext: false);
var task1 = tsc.Task;
var task2 = Task.Delay(1_000);
var task3 = Task.Run(async () => { await Task.Delay(1_000); });
var task4 = Task.Run(() => {});
var task5 = Task.Run(() => {});

var res = await Task.WhenAny(task1, task2, task3, task5, task4);

Console.WriteLine($"Task1: {task1.Id}");
Console.WriteLine($"Task2: {task2.Id}");
Console.WriteLine($"Task3: {task3.Id}");
Console.WriteLine($"Task4: {task4.Id}");
Console.WriteLine($"Task5: {task5.Id}");
Console.WriteLine($"res: {res.Id}");

what does await Task.WhenAny(task1,task2,task3); if more than one task has finished before the code reaches that await statement?

In my experiments res.Id was always task4.Id.

Is it a random task?

The above example is a bit bias. So, lets change the task4 and task5 to Task.Delay(10);.
In this case res.Id will be task4.Id or task5.Id depending on scheduling.

The first from the arguments that finished?

No, it does not matter whether you add task4 before or after task5 to the parameter list of Task.WhenAny.

A list of the tasks that have completed?

No. If you need that a simple Linq query can return that

List<Task> tasks = [task1, task2, task3, task5, task4];
var res = await Task.WhenAny(tasks);

var completedTaskIds = tasks.Where(t => t.Status == TaskStatus.RanToCompletion).Select(t => t.Id).ToArray();