shay__
shay__

Reputation: 3990

How to use Task.WhenAll() for multiple Lists of different return types?

I have two sets of Tasks, each with different result type:

IEnumerable<Task<T1>> set1 = GetTasksOfT1();
IEnumerable<Task<T2>> set2 = GetTasksOfT2();

Now I would like to await both sets in one line, but I had to project set1 with cast:

 await Task.WhenAll(set1.Select(p => p as Task).Concat(set2));

That's because I get this error if I don't use casting:

IEnumerable<Task<T1>>' does not contain a definition for 'Concat' 
and the best extension method overload 'Queryable.Concat<Task<T2>>(IQueryable<Task<T2>>, IEnumerable<Task<T2>>)' 
requires a receiver of type 'IQueryable<Task<T2>>'

Which is obvious.

So is there a way to use a single WhenAll() without casting?

Edit: The return types T1 and T2 are important - I consume them after the tasks are awaited:

//After await Task.WhenAll(...):
var t1Results = await set1; // all set1 Tasks are completed by now 
var t2Results = await set2; // same
DoSomethingWithResults(t1Results, t2Results);

Upvotes: 8

Views: 5557

Answers (4)

Paulo Morgado
Paulo Morgado

Reputation: 14846

Although being debatable if creating an array is better than concatenating two enumerables, you could leave the concatenation out of the equation:

await Task.WhenAll(set1, set2);

But, since what you need the is the result of evaluation of each sequence of tasks, I wouldn't bother to await them all as a single set:

DoSomethingWithResults(await set1, await set2);

Upvotes: 0

svick
svick

Reputation: 244837

If you need the results of both sets of Tasks, then it might be simpler to not wait for the all the lists.

I imagine your code (using usr's suggestion) would look like this:

IEnumerable<Task<T1>> set1 = GetTasksOfT1();
IEnumerable<Task<T2>> set2 = GetTasksOfT2();

await Task.WhenAll(set1.Concat<Task>(set2));

var t1Results = await Task.WhenAll(set1);
var t2Results = await Task.WhenAll(set2);

Bu here, the combined await doesn't really serve any purpose. If you remove it, both result variables will still be initialized correctly, because they have their own awaits.

Upvotes: 1

usr
usr

Reputation: 171178

Actually, the compiler just complains about type inference failure. You can help it with that:

IEnumerable<Task<int>> set1 = null;
IEnumerable<Task<string>> set2 = null;

set1.Concat((IEnumerable<Task>)set2); //#1
((IEnumerable<Task>)set1).Concat(set2); //#2
set1.Concat<Task>(set2); //#3

There are many other ways to provide the type information. This works because IEnumerable is covariant. This will call Enumerable.Concat<Task>(IEnumerable<Task>, IEnumerable<Task>).

Upvotes: 4

Christos
Christos

Reputation: 53958

If both methods, GetTasksOfT1 and GetTasksOfT2 wouldn't return Task<T> but Task, you woulnd't have any problem.

So I suggest either refactor these method to return a sequence of Task objects, IEnumerable<Task> or pickup the Kote's solution.

Upvotes: 2

Related Questions