4

I am using SignalR. The function on the Hub often return a Task. I now have a function that will add a connection to a bunch of groups. I would like to return a Task that represents all of these Tasks.

I found a perfect function for that: Task.WhenAll. However this is a new function in .NET 4.5 and I am still stuck on .NET 4.

Hence I decided to write my own version of it until we can move to .NET 4.5. Because there are often some caveats when it comes to multithreading (e.g. thread pool stuff), I am not sure if my implementation is correct:

public static Task WhenAll(IEnumerable<Task> tasks)
{
    return Task.Factory.StartNew(() => Task.WaitAll(tasks.ToArray()));
}

Functionally, it works I think, but don't I get an extra blocked thread for the new Task? Or is this unavoidable?

Edit: Here is how I would use it with SignalR:

public static Task Add(this IGroupManager groupManager, string connectionId,
                                                   IEnumerable<string> groups)
{
   return WhenAll(groups.Select(group => groupManager.Add(connectionId, group)));
}
cuongle
  • 74,024
  • 28
  • 151
  • 206
Matthijs Wessels
  • 6,530
  • 8
  • 60
  • 103
  • 1
    Out of curiousity, have you tried decompiling or viewing the source for .NET 4.5's version to see how close you are? (http://referencesource.microsoft.com/netframework.aspx) – James Michael Hare Sep 20 '12 at 14:49
  • No I have not done that. I've never done that actually. Downloading JustDecompile now. – Matthijs Wessels Sep 20 '12 at 14:53
  • I'm downloading the source, hoped to have an answer for you already, but it's taking a wee bit of time to download... – James Michael Hare Sep 20 '12 at 14:53
  • Wow, it looks like I'm pretty far off :P. It seems to me the implementation is similar to what svick saw in the implementation of ContinueWhenAll. I.e. schedule continuation for each task. However, it's quite an elaborate function and I didn't get everything that was going on. – Matthijs Wessels Sep 20 '12 at 15:39

2 Answers2

9

Your solution will work fine, but you're right that it would block a thread the whole time.

I think the simplest way to efficiently implement WhenAll() on .Net 4.0 is to use ContinueWhenAll(). It performs an action when all Tasks from a collection are finished and returns a Task representing that action. Since we want just that Task, we don't need the action, passing an empty lambda will work:

public static Task WhenAll(IEnumerable<Task> tasks)
{
    return Task.Factory.ContinueWhenAll(tasks.ToArray(), _ => {});
}
svick
  • 236,525
  • 50
  • 385
  • 514
  • I wonder how that works underwater though. I.e. does it do the job without creating an extra thread? – Matthijs Wessels Sep 20 '12 at 15:00
  • I didn't look at the source yet, but I'm quite sure that it doesn't waste a thread by blocking. – svick Sep 20 '12 at 15:11
  • 1
    I checked now, and it really doesn't block a thread, it schedules a continuation for each task in the collection, and when all execute, the action executes. – svick Sep 20 '12 at 15:17
  • 1
    @svick, I don't think this will equivalent to `.WhenAll` with respect to the returned task's Status. For `WhenAll` if any of the returned Tasks are faulted, the returned tasks is faulted, if any of the returned Tasks are cancelled, the returned task is cancelled. In this version, the returned Task will be in the RanToCompletion state regardless of the completion state of its antecedents. To make it equivalent, the action would need to be non-trivial and take care of this logic. – Matt Smith Apr 11 '13 at 14:32
5

While you're targeting .Net 4.0, if you can use VS2012, then a simpler/better option (IMHO) is to use NuGet to install the async targeting pack and then you can use WhenAll (TaskEx.WhenAll in that case, since it can't modify the Task that's in the 4.0 framework).

As a significant added bonus, you can then use async/await in your .Net 4.0 code as well :)

Community
  • 1
  • 1
James Manning
  • 13,429
  • 2
  • 40
  • 64