Deva Raja
Deva Raja

Reputation: 93

Using WhenAny in asynchronous programming in C#

I am studying asynchronous programming from Microsoft documentation and I don't understand the following example which uses the WhenAny method:

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };

while (breakfastTasks.Count > 0)
{
    Task finishedTask = await Task.WhenAny(breakfastTasks);

    if (finishedTask == eggsTask)
    {
        Console.WriteLine("Eggs are ready");
    }
    else if (finishedTask == baconTask)
    {
        Console.WriteLine("Bacon is ready");
    }
    else if (finishedTask == toastTask)
    {
        Console.WriteLine("Toast is ready");
    }

    await finishedTask;

    breakfastTasks.Remove(finishedTask);
}

You can find it here.

The problem with this code is that eggsTask refers to a Task<Egg> object, baconTask refers to a Task<Bacon> object, and toastTask refers to a Task<Toast> object.

Then he puts these 3 variables to a List which then is an argument in Task.WhenAny(breakfastTasks) method.

Now, I believe that the non-generic overload of WhenAny is used, which you can see here. Specifically I believe this overload is used:

public static System.Threading.Tasks.Task<System.Threading.Tasks.Task> WhenAny(System.Collections.Generic.IEnumerable<System.Threading.Tasks.Task> tasks);

As you can see the parameter accepts IEnumerable<Task>. Also this overload returns a Task<Task> object, which when completed becomes a Task object. So in this method you put in Task objects and when one is completed, the method returns a Task object which is the one Task object that completed first.

However, when a Task<Egg> object completes, it becomes an Egg object, when a Task<Bacon> object completes, it becomes a Bacon object, and when a Task<Toast> object completes, it becomes a Toast object.

So, the line below doesn't seem right:

Task finishedTask = await Task.WhenAny(breakfastTasks);

Suppose eggsTask completes first, it gives an Egg object which is supposed to be returned by the method, but the method when completed returns only a Task object.

Could you please explain?

Upvotes: 0

Views: 88

Answers (1)

Progman
Progman

Reputation: 19555

The overload of Task.WhenAny() you are using has a return type of Task<Task>. In combination with the await keyword you will get a Task reference that finished from the given list of tasks to observe.

This returned Task reference is referencing one of your existing task references eggsTask (Task<Egg>), baconTask (Task<Bacon>) or toastTask (Task<Toast>). It does not return any Egg, Bacon or Toast reference. To get the result from the finished task you still have to call await on the Task<...> instance. As an example, you could wrote

if (finishedTask == eggsTask)
{
    Console.WriteLine("Eggs are ready");
    Egg egg = await eggsTask;
}

to get the Egg reference from that finished task. And since the task is already finished, this await call will immediately return the Egg reference (and save it in the egg variable).

Keep in mind that Task<TResult> is extending from Task. So all your Task<Egg>, Task<Bacon> and Task<Toast> references could be saved in a variable of type Task. This is happening in the line

Task finishedTask = await Task.WhenAny(breakfastTasks);

However, this will "lose" the ability to get the result from this variable, as there is no generic type anymore. But as long as you have the original task references, you can get the results from these variables instead (eggsTask, baconTask and toastTask) as shown above.

Upvotes: 4

Related Questions