Awaiting Multiple Tasks With Different Results

Awaiting multiple Tasks with different results

After you use WhenAll, you can pull the results out individually with await:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

You can also use Task.Result (since you know by this point they have all completed successfully). However, I recommend using await because it's clearly correct, while Result can cause problems in other scenarios.

await multiple tasks and access results

You are iterating (on the foreach) to a Task variable (which doesn't have a typed Result), you need to iterate to a Task<Service> variable (or use var, since your tasks list is already typed), that is:

foreach (var t in tasks)
{
services.Add(t.Result);
}

How to run two lists of tasks with different return types at the same time

Note that Task<T> actually inherits from the non-generic Task so you cast all your generic tasks to the base class, something like this:

var nonGenericTasks = catTasks
.Cast<Task>()
.Concat(dogTasks.Cast<Task>());

await Task.WhenAll(nonGenericTasks);

Collecting results of async within lambda

Another way to do it is to project each long ID to a Task<ValueTuple<long, bool>>, instead of projecting it to a Task<bool>. This way you'll be able to filter the results using pure LINQ:

private async Task<long[]> GetValidIds3(long[] ids)
{
IEnumerable<Task<(long Id, bool IsValid)>> tasks = ids
.Select(async id =>
{
bool isValid = await CheckValidIdAsync(id).ConfigureAwait(false);
return (id, isValid);
});
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
return results
.Where(e => e.IsValid)
.Select(e => e.Id)
.ToArray();
}

The above GetValidIds3 is equivalent with the GetValidIds1 in your question. It returns the filtered IDs in the same order as the original ids. On the contrary the GetValidIds2 doesn't guarantee any order. If you have to use a concurrent collection, it's better to use a ConcurrentQueue<T> instead of a ConcurrentBag<T>, because the former preserves the insertion order. Even if the order is not important, preserving it makes the debugging easier.

How to pass additional context information along with tasks to Task.WhenAll?

You can either do the zipping as you go:

public async Task<IEnumerable<(string, SomeObject)>>
DoManyThingsAsync(IEnumerable<string> someListOfStrings, bool condition)
{
var tasks = someListOfStrings
.Select(async item =>
condition ?
(item, await DoSomethingAsync(item)) :
(item, await DoSomethingElseAsync(item)))
.ToList();
return await Task.WhenAll(tasks);
}

Or, you can keep the input as a separate collection and zip it later:

public async Task<IEnumerable<(string, SomeObject)>>
DoManyThingsAsync(IEnumerable<string> someListOfStrings, bool condition)
{
// Reify input so we don't enumerate twice.
var input = someListOfStrings.ToList();

var tasks = input
.Select(item =>
condition ?
DoSomethingAsync(item) :
DoSomethingElseAsync(item))
.ToList();
var taskResults = await Task.WhenAll(tasks);

return input.Zip(taskResults, (item, taskResult) => ((item, taskResult)));
}

Waiting multiple Tasks on .NET

Once you have successfully awaited Task.WhenAll, you know that the tasks are completed, so you can use task1.Result, task2.Result, etc. to get the individual task results.



Related Topics



Leave a reply



Submit