73

I start a few parallel tasks, like this:

var tasks =
    Enumerable.Range(1, 500)
    .Select(i => Task.Factory.StartNew<int>(ProduceSomeMagicIntValue))
    .ToArray();

and then join them with

Task.WaitAll(tasks);

On this last line I get a blue squiggly marker under tasks, with a warning message:

Co-variant array conversion from Task[] to Task[] 
can cause run-time exception on write operation.

I understand why I get this message, but is there a way around that? (for example, like a generic version of Task.WaitAll()?)

Cristian Lupascu
  • 39,078
  • 16
  • 100
  • 137
  • 3
    In this case, the conversion is safe, because `WaitAll()` won't write to the array. Is there any reason why you want to avoid it? – svick Apr 06 '12 at 13:58
  • 17
    Also, .Net 4.5 will contain [`Task.WhenAll()`](http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.whenall%28v=vs.110%29.aspx) that returns a single `Task` that completes when all the `Task`s in the collection complete. And it also has a generic version and it works on any `IEnumerable` of `Task`s. – svick Apr 06 '12 at 14:02
  • @svick thx for the tip. looks like they renamed what you're talking about to WhenAll so you can just say 'await Task.WhenAll(task1, task2);' – Simon_Weaver Sep 10 '12 at 19:34
  • any chance you'd consider accepting a different answer? I personally found others more worthy than the one you've chosen as "accepted". – Quibblesome Nov 21 '18 at 18:34
  • 1
    @Quibblesome Done. – Cristian Lupascu Nov 22 '18 at 07:54

4 Answers4

34

A generic method of Task.WaitAll would imply that all Tasks would have to return the same type which would be extremely limited usefulness. Writting something like that could be done manually (see Bas Brekelmans answer), but this wont allow ContinueWith or cancellation without alot of work.

A simple solution if you aren't using the array for anything else is

  .ToArray<Task>();
MerickOWA
  • 7,453
  • 1
  • 35
  • 56
  • +1 that's a possibility, but in my case, I actually use the array to get the `int` results: `var results = tasks.Select(task => task.Result)` – Cristian Lupascu Apr 06 '12 at 13:45
  • 1
    @W0lf then simply do an extra .ToArray() before you pass it to Task.WaitAll, any possible assignment to this copied Task[] can't cause a runtime exception – MerickOWA Apr 06 '12 at 14:04
30

I'm pretty sure it's a safe operation even with the warning, but if you really wanted to get around it a better option than creating your own implementation would be just to convert your tasks parameter into the type it wants:

Task.WaitAll(tasks.Cast<Task>().ToArray())

That kills the blue squiggles for me, lets me keep my tasks variable generic and doesn't force me to create a whole lot of new scary code that's ultimately unnecessary.

DMac the Destroyer
  • 5,240
  • 6
  • 36
  • 56
  • 1
    This worked perfectly for me. Tested it both with tasks that ran to completion, and tasks that timed out, using `var ifAllTasksRanToCompletion = Task.WaitAll(tasks.Cast().ToArray(), timeout: TimeSpan.FromSeconds(20));`. – Contango Sep 25 '14 at 15:16
  • But this may be slower (when ran in parallel, PLINQ) due the `.ToArray()` as said here: https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/understanding-speedup-in-plinq – Nicolas VERHELST Nov 09 '22 at 13:42
21

A BETTER AND SIMPLER ANSWER

Actually there IS a similar generic overload:

Task all = Task.WhenAll(tasks)

This is different in this it returns a Task that will complete after all tasks completed. so you can use await on it, or Wait(), whatever you want.

Look at the signature:

Overloads

--------- NON GENERIC OVERLOADS --------------

WhenAll(IEnumerable<Task>) Creates a task that will complete when all of the Task objects in an enumerable collection have completed.

WhenAll(Task[]) Creates a task that will complete when all of the Task objects in an array have completed.

--------- GENERIC OVERLOADS --------------

WhenAll<TResult>(IEnumerable<Task<TResult>>) Creates a task that will complete when all of the Task<TResult> objects in an enumerable collection have completed.

WhenAll<TResult>(Task<TResult>[]) Creates a task that will complete when all of the Task<TResult> objects in an array have completed.

Yitzchak
  • 3,303
  • 3
  • 30
  • 50
6

You can create an extension method to do this.

I do not know the exact implementation of WaitAll, but we can assume it waits for every item to complete:

static class TaskExtensions
{
    public static void WaitAll<T>(this Task<T>[] tasks)
    {
        foreach (var item in tasks)
        {
            item.Wait();
        }
    }
}

Then call, from your current code:

tasks.WaitAll();

Edit

The actual implementation is a bit more complex. I have omitted the code from this answer because it is fairly long.

http://pastebin.com/u30PmrdS

You can modify this to support generic tasks.

Bas
  • 26,772
  • 8
  • 53
  • 86
  • 2
    The "actual implementation" is quite non-trivial. Not to mention, you loose out (or need to re-code) if the .NET Framework implementation has any bug fixes or performance improvements. The solutions from @MerickOWA and DMac the Destroyer (in that order) resolve the OP's issue very simply. – Mike Jul 20 '17 at 07:13
  • 1
    This is really obviously wrong - you're running your tasks *serially* here, which isn't what the real `WaitAll` does. – Mark Amery Aug 31 '17 at 12:12
  • 1
    @MarkAmery this assumes the tasks are already dispatched when they are fed into this method. – Bas Aug 31 '17 at 17:30
  • 1
    If people insist on building their own (instead of using one of the other solutions that convert the generic collection into an array of Task) this is a better source, with full comments and links to all of the internal code that you will also need to duplicate to make it work: https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,72b6b3fa5eb35695 – Mike Sep 07 '17 at 04:38