WaitAll vs WhenAll
Task.WaitAll
blocks the current thread until everything has completed.
Task.WhenAll
returns a task which represents the action of waiting until everything has completed.
That means that from an async method, you can use:
await Task.WhenAll(tasks);
... which means your method will continue when everything's completed, but you won't tie up a thread to just hang around until that time.
WhenAny vs WhenAll vs WaitAll vs none, given that results are being used immediately
The simple await will perform each item one after another, essentially synchronously - this would be the slowest.
WhenAll
will wait for all of tasks to be done - the runtime will be whatever the longest single task is.
Do not use WaitAll
- it is synchronous, just use WhenAll
WhenAny
allows you to handle each task as it completes. This in will be faster than WhenAll
in some cases, depending on how much processing you have to do after the task.
IMO, unless you need to start post processing immediately when each task complets, WhenAll
is the simplest/cleanest approach and would work fine in most scenarios.
What is the difference between WaitAll and WhenAll?
MSDN does a good job of explaining this. The difference is pretty unambiguous.
Task.WhenAll:
Creates a task that will complete when all of the supplied tasks have completed.
Task.WaitAll:
Waits for all of the provided Task objects to complete execution.
So, essentially, WhenAll
gives you a task that isn't done until all of the tasks you give it are done (and allows program execution to continue immediately), whereas WaitAll
just blocks and waits for all of the tasks you pass to finish.
Is Task.WhenAll(taskList).Wait() the same as Task.WaitAll(taskList)?
Let's find out by experimentation:
var task1 = Task.FromResult(13);
var task2 = Task.FromCanceled<int>(new CancellationToken(true));
var task3 = Task.FromCanceled<int>(new CancellationToken(true));
var task4 = Task.FromException<int>(new ApplicationException());
var task5 = Task.FromException<int>(new OverflowException());
Test("Successful+Canceled+Canceled", new[] { task1, task2, task3 });
Test("Successful+Failed+Failed", new[] { task1, task4, task5 });
Test("Successful+Canceled+Failed+Failed", new[] { task1, task2, task4, task5 });
Test("Successful+Canceled+Canceled+Failed", new[] { task1, task2, task3, task4 });
static void Test(string title, Task<int>[] tasks)
{
Console.WriteLine();
Console.WriteLine(title);
try { Task.WaitAll(tasks); }
catch (AggregateException ex)
{
Console.WriteLine($"WaitAll(): {ToString(ex)}");
}
try { Task.WhenAll(tasks).Wait(); }
catch (AggregateException ex)
{
Console.WriteLine($"WhenAll.Wait(): {ToString(ex)}");
}
}
static string ToString(AggregateException aex) {
return $"({aex.InnerExceptions.Count}) " +
String.Join(", ", aex.InnerExceptions.Select(ex => ex.GetType().Name));
}
Output:
Successful+Canceled+Canceled
WaitAll(): (2) TaskCanceledException, TaskCanceledException
WhenAll.Wait(): (1) TaskCanceledException
Successful+Failed+Failed
WaitAll(): (2) ApplicationException, OverflowException
WhenAll.Wait(): (2) ApplicationException, OverflowException
Successful+Canceled+Failed+Failed
WaitAll(): (3) TaskCanceledException, ApplicationException, OverflowException
WhenAll.Wait(): (2) ApplicationException, OverflowException
Successful+Canceled+Canceled+Failed
WaitAll(): (3) TaskCanceledException, TaskCanceledException, ApplicationException
WhenAll.Wait(): (1) ApplicationException
Try it on Fiddle.
What we see is that the Task.WaitAll()
method propagates the exceptions of the tasks as they are, while the Task.WhenAll()
.Wait()
approach propagates only a single TaskCanceledException
, and only when no other type of exception has occurred.
It should also be mentioned that with the Task.WaitAll
you get more options out of the box, like millisecondsTimeout
, or cancellationToken
, or both.
Using Task.WaitAll method instead of await keyword
Task.WaitAll()
will block you main thread. You should use await Task.WhenAll()
WhenAll vs WaitAll in parallel
You are starting tasks inside a Parallel.ForEach
loop which you should avoid. The whole point of Paralle.ForEach
is to parallelize many small but intensive computations across the available CPU cores and starting a task is not an intensive computation. Rather it creates a task object and stores it on a queue if the task pool is saturated which it quickly will be with 1000 tasks being starteed. So now Parallel.ForEach
competes with the task pool for compute resources.
In the first loop that is quite slow it seems that the scheduling is suboptimal and very little CPU is used probably because of Task.WhenAll
inside the Parallel.ForEach
. If you change the Parallel.ForEach
to a normal for loop you will see a speedup.
But if you code really is as simple as a Compute
function without any state carried forward between iterations you can get rid of the tasks and simply use Parallel.ForEach
to maximize performance:
Parallel.For(0, 100, (i, s) =>
{
Enumerable.Range(1, 1000).Select(n => Compute(n)).SelectMany(r => r).ToList();
});
As to why Task.WhenAll
performs much worse you should realize that this code
tasks.Select(t => t.Result).SelectMany(r => r).ToList();
will not run the tasks in parallel. The ToList
basically wraps the iteration in a foreach
loop and the body of the loop creates a task and then waits for the task to complete because you retrieve the Task.Result
property. So each iteration of the loop will create a task and then wait for it to complete. The 1000 tasks are executed one after the other and there is very little overhead in handling the tasks. This means that you do not need the tasks which is also what I have suggested above.
On the other hand, the code
Task.WhenAll(tasks).Result.SelectMany(result => result).ToList();
will start all the tasks and try to execute them concurrently and because the task pool is unable to execute 1000 tasks in parallel most of these tasks are queued before they are executed. This creates a big management and task switch overhead which explains the bad performance.
With regard to the final question you added: If the only purpose of the outer task is to start the inner tasks then the outer task has no useful purpose but if the outer tasks are there to perform some kind of coordination of the inner tasks then it might make sense (perhaps you want to combine Task.WhenAny
with Task.WhenAll
). Without more context it is hard to answer. However, your question seems to be about performance and starting 100,000 tasks may add considerable overhead.
Parallel.ForEach
is a good choice if you want to perform 100,000 independent computations like you do in your example. Tasks are very good for executing concurrent activities involving "slow" calls to other systems where you want to wait for and combine results and also handle errors. For massive parallelism they are probably not the best choice.
WhenAll vs New object with multiple awaits
Yes, there's enormous difference between the two.
In the first case, all 3 tasks will proceed in parallel and independently assuming there's no resource contention among them.
However, in the second case they'll only proceed one by one.
The remarks section for
WhenAll()
is also significant.Depending upon the synchronization context in effect, if any at all, the point where you experience a potential deadlock is also different for both.
The point where an exception thrown by the task is visible differs.
Task.WhenAll - When to use this
WaitAll
is a void
function that lets your code wait for completion of multiple tasks right away, at the time when you call it.
WhenAll
, on the other hand, produces a Task
that will wait for other tasks when it is its time to run. For example, if you are building a Task
that needs to initialize a couple of things, and then run some computation, you can do it like this:
var initThenRun = Task
.WhenAll(initTask1, initTask2, initTask3)
.ContinueWith(computationTask);
Now the initThenRun
task will run the three initialization tasks, wait for all of them to complete, and only then proceed to the computationTask
.
Related Topics
How to Check For Internet Connectivity Using .Net
How to Perform a Left Outer Join Using Linq Extension Methods
Why and How to Avoid Event Handler Memory Leaks
What Does "Use of Unassigned Local Variable" Mean
Validating an Xml Against Referenced Xsd in C#
Deserialize Json to C# Classes
Why Does Adding a New Value to List≪≫ Overwrite Previous Values in the List≪≫
What Are Automatic Properties in C# and What Is Their Purpose
Change Default App.Config At Runtime
Given a Filesystem Path, Is There a Shorter Way to Extract the Filename Without Its Extension
Check If a Class Is Derived from a Generic Class
Difference in Months Between Two Dates
Json.Net Serialization of Type With Polymorphic Child Object
Converting String to Byte Array in C#