Waitall VS Whenall

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.

  1. 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.

  2. The remarks section for WhenAll() is also significant.

  3. Depending upon the synchronization context in effect, if any at all, the point where you experience a potential deadlock is also different for both.

  4. 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



Leave a reply



Submit