In this post, we’re going to see when we should use
Task.WhenAll. I’m also going to explain the differences between
Task.WaitAll. Then I move on to show some example of good and bad usage of these constructs. We’re also going to see some situations such as when we need immediate processing and solution for it.
When Should You Use Task.WhenAny
Task.WhenAny when we have a collection of tasks, but we only interested in the first finished task. It can happen for example when we have couple of async API that all of them do the same thing. But we want to receive the result from the one that return the result first.
Another scenario is when we need a back up mechanism. So if one of those tasks failed the other one completes and gives us the result. For that reason when we receive a completed task to await, if any exception happens, it is not propagated to the returned task. Remember that when we call the method on line 4 by calling
GetHtmlResponseAsync, the method is on its way to get the result. So
WhenAny in this case return the first task that is finished. Finally by awaiting the returned task we get the actual results.
Another important thing to note is that when the first task is finished and returned, others are still running. What happens here if we don’t cancel them is that they run to completion and get abandoned. So in these situations when we don’t need other tasks we can cancel them.
What we did here is we take a cancellation token as an argument and pass them to
GetAsync. So now as you can see in
UseFirstRespondingUrlWithCancellationAsync method, we can cancel the rest of the tasks when we get the needed result.
When Should You Use Task.WhenAll
We can use
Task.WhenAll to wait for a set of tasks to complete. We can also wait for each task in a loop. But that’ll be inefficient since we dispatch the tasks one at the time.
As you can see in the method above, we call
GetStringAsync method one at the time. What we want is to dispatch the tasks that get the strings simultaneously. In these situations we can use
Task.WhenAll. So the above code can be refactored as this.
In the code above we aggregated our tasks into a list and asynchronously waited for all of them. Another important thing is how
Task.WhenAll deal with exceptions. When one or more exceptions are thrown, the exceptions are going to be place on the task as a
AggregateException. If we use try catch in this situation, we only get the first exception from the collection of exceptions. If we need all of them, we can’t await the task and get the result both on the same line.
In the above code excerpt, we assign the task to a variable. We then await it and then use the previous task variable to get all exceptions.
Processing Tasks As They Complete
Another situation that might come up once in a while is when we need to do some processing on a task as soon as it complete. Because right now the way we’re using
Task.WhenAll, we get the results from all tasks at once. We don’t have the opportunity to make any changes to the result before firing off the next. The best solution is to create an intermediary method that do the await and process for us and await that method instead.
As you can see in the code above, the
AwaitAndProcessAsync method is the method that does the await and extra processing for us. We first create the tasks, then pipe them though
AwaitAndProcessAsync method and get a list of Task. We then use the
Task.WhenAll to wait for all of those task and processing them as soon as they return a value. There is an article by Stephen Toub that goes into much more detail.
Task.WhenAny vs Task.WaitAny
The main difference between these two is that
Task.WaitAny is a blocking operation. It is the same as using
task.Result. The difference is that it takes multiple tasks and return the result from the one that completed first.
Task.WaitAny can be used in some situations, but they are rare. Maybe when we want to block the operation for example in console app. But even that’s not acceptable these days. Because since C# 7.1 we can use
async Task Main(string args).
Task.WhenAll vs Task.WaitAll
Task.WaitAll also is a blocking operation. It behaves the same as
task.Wait(), except it takes a collection of tasks and wait for all of them to finish. Like its counter part,
Task.WaitAll is use rarely if at all. In most situations the non blocking
Task.WhenAll is what we should be using.
In this post I explained when and how to use
Task.WhenAll. We also saw their differences with
Task.WaitAll method and some best practices regarding using them. I also briefly gone though situations and solutions when we use Task.WhenAll and we need to do some processing immediately after a task returns value.