Get the result of multiple tasks in a ValueTuple and WhenAll
In .NET, you can use Task.WhenAll
to wait for multiple tasks. Once the tasks are completed, you can get the results using .Result
or by awaiting them.
Task<int> task1 = Task.Run(() => 1);
Task<string> task2 = Task.Run(() => "meziantou");
await Task.WhenAll(task1, task2);
var task1Result = task1.Result; // or await task1
var task2Result = task2.Result; // or await task2
I don't really want write this kind of code. Instead, I would like to get the results directly from the WhenAll
method. Something like the following:
Task<int> task1 = Task.Run(() => 1);
Task<string> task2 = Task.Run(() => "meziantou");
// This doesn't work
var (task1Result, task2Result) = await Task.WhenAll(task1, task2);
You can write custom WhenAll
methods that return a ValueTuple with the results of the tasks.
public static class TaskEx
{
public static async Task<(T0, T1)> WhenAll<T0, T1>(Task<T0> task0, Task<T1> task1)
{
await Task.WhenAll(task0, task1).ConfigureAwait(false);
// It's ok to use task.Result here as the task is completed
return (task0.Result, task1.Result);
}
public static async Task<(T0, T1, T2)> WhenAll<T0, T1, T2>(Task<T0> task0, Task<T1> task1, Task<T2> task2)
{
await Task.WhenAll(task0, task1, task2).ConfigureAwait(false);
return (task0.Result, task1.Result, task2.Result);
}
public static async Task<(T0, T1, T2, T3)> WhenAll<T0, T1, T2, T3>(Task<T0> task0, Task<T1> task1, Task<T2> task2, Task<T3> task3)
{
await Task.WhenAll(task0, task1, task2, task3).ConfigureAwait(false);
return (task0.Result, task1.Result, task2.Result, task3.Result);
}
public static async Task<(T0, T1, T2, T3, T4)> WhenAll<T0, T1, T2, T3, T4>(Task<T0> task0, Task<T1> task1, Task<T2> task2, Task<T3> task3, Task<T4> task4)
{
await Task.WhenAll(task0, task1, task2, task3, task4).ConfigureAwait(false);
return (task0.Result, task1.Result, task2.Result, task3.Result, task4.Result);
}
public static async Task<(T0, T1, T2, T3, T4, T5)> WhenAll<T0, T1, T2, T3, T4, T5>(Task<T0> task0, Task<T1> task1, Task<T2> task2, Task<T3> task3, Task<T4> task4, Task<T5> task5)
{
await Task.WhenAll(task0, task1, task2, task3, task4, task5).ConfigureAwait(false);
return (task0.Result, task1.Result, task2.Result, task3.Result, task4.Result, task5.Result);
}
public static async Task<(T0, T1, T2, T3, T4, T5, T6)> WhenAll<T0, T1, T2, T3, T4, T5, T6>(Task<T0> task0, Task<T1> task1, Task<T2> task2, Task<T3> task3, Task<T4> task4, Task<T5> task5, Task<T6> task6)
{
await Task.WhenAll(task0, task1, task2, task3, task4, task5, task6).ConfigureAwait(false);
return (task0.Result, task1.Result, task2.Result, task3.Result, task4.Result, task5.Result, task6.Result);
}
}
You can now use it this way:
Task<int> task1 = Task.Run(() => 1);
Task<string> task2 = Task.Run(() => "meziantou");
Task<string> task3 = Task.Run(() => 'm');
// This works :)
var (t1Result, t2Result, t3Result) = await TaskEx.WhenAll(task1, task2, task3);
The previous code can be improved by using a new GetAwaiter
extension method. This way you can directly await
an instance of ValueTuple<Task<T1>, Task<T2>>
.
public static class TaskEx
{
public static TaskAwaiter<(T1, T2)> GetAwaiter<T1, T2>(this ValueTuple<Task<T1>, Task<T2>> tasks)
{
return WhenAll(tasks.Item1, tasks.Item2).GetAwaiter();
}
public static TaskAwaiter<(T1, T2, T3)> GetAwaiter<T1, T2, T3>(this ValueTuple<Task<T1>, Task<T2>, Task<T3>> tasks)
{
return WhenAll(tasks.Item1, tasks.Item2, tasks.Item3).GetAwaiter();
}
...
}
With the previous extension methods, you can use the following code:
Task<int> task1 = Task.Run(() => 1);
Task<string> task2 = Task.Run(() => "meziantou");
Task<string> task3 = Task.Run(() => 'm');
// This works :)
var (t1Result, t2Result, t3Result) = await (task1, task2, task3);
The full code is available on GitHub: Meziantou.Framework - TaskEx.WhenAll
Do you have a question or a suggestion about this post? Contact me!