WaitAll for tasks from dictionary and get result, including Dictionary key

Viewed 244

I have to modify the following code. I have given a list of tasks (httpClient.Run method returns Task). I have to run them and wait until all of them are done. Later on I need to collect all results and build response.

var tasks = new Dictionary<string, Task<string>>();
tasks.Add("CODE1", service.RunTask("CODE1"));
tasks.Add("CODE2", service.RunTask("CODE2"));
tasks.Add("CODE3", service.RunTask("CODE3"));
//...

var result = await Task.WhenAll(tasks.Values); // how to get CODE (dictionary KEY) here
// build response

The problem above is that when we get results we have lost exactly which task was run. results is string array, but I need, for instance,KeyValuePair array. We need to know which task (CODE) was run, so that we can build result properly.

3 Answers

You can use async in a Select lambda to transform the KeyValuePair<T1, Task<T2>s into Task<KeyValuePair<T1, T2>>s.

var resultTasks = tasks.Select(async pair => KeyValuePair.Create(pair.Key, await pair.Value));
IReadOnlyCollection<KeyValuePair<string, string>> results = await Task.WhenAll(resultTasks);

Something like this ought to do it. This is written assuming you do the Task.WhenAll() call first. Otherwise, you'll block the thread when you start to enumerate over resultsWithKeys.

await Task.WhenAll(tasks.Values());
var resultsWithKeys = tasks.Select(
    x => new
    {
        Key = x.Key, 
        Result = x.Value.Result
    });

foreach (var result in resultsWithKeys)
    Console.WriteLine($"{result.Key} - {result.Result.SomeValue}");

The Dictionary<string, Task<string>> contains tasks that do not propagate the key as part of their Result. If you prefer to have tasks that include the key in their result, you'll have to create a new dictionary and fill it with new tasks that wrap the existing tasks. The TResult of these new tasks can be a struct of your choice, for example a KeyValuePair<string, string>. Below is an extension method WithKeys that allows to create easily the new dictionary with the new tasks:

public static Dictionary<TKey, Task<KeyValuePair<TKey, TValue>>> WithKeys<TKey, TValue>(
    this Dictionary<TKey, Task<TValue>> source)
{
    return source.ToDictionary(e => e.Key,
        async e => KeyValuePair.Create(e.Key, await e.Value.ConfigureAwait(false)),
        source.Comparer);
}

Usage example:

KeyValuePair<string, string>[] result = await Task.WhenAll(tasks.WithKeys().Values);
Related