How to moisten the dictionary with the results of asynchronous calls?

Suppose I have code that looks like this:

public async Task<string> DoSomethingReturnString(int n) { ... } int[] numbers = new int[] { 1, 2 , 3}; 

Suppose I want to create a dictionary containing the result of calling DoSomethingReturnString for each number like this:

 Dictionary<int, string> dictionary = numbers.ToDictionary(n => n, n => DoSomethingReturnString(n)); 

This will not work because DoSomethingReturnString returns a Task<string> , not a string . Intellisense suggested that I try to indicate that my lambda expression is asynchronous, but that also did not help solve the problem.

+14
c # async-await
source share
4 answers

If you insist on doing this with linq, Task.WhenAll is the key to “hydrating” the dictionary:

 int[] numbers = new int[] { 1, 2 , 3}; KeyValuePair<int, string>[] keyValArray = //using KeyValuePair<,> to avoid GC pressure await Task.WhenAll(numbers.Select(async p => new KeyValuePair<int, string>(p, await DoSomethingReturnString(p)))); Dictionary<int, string> dict = keyValArray.ToDictionary(p => p.Key, p => p.Value); 
+6
source share

LINQ methods do not support asynchronous actions (for example, asynchronous value selectors), but you can create them yourself. Here is a reusable ToDictionaryAsync extension method that supports an asynchronous value selector:

 public static class ExtensionMethods { public static async Task<Dictionary<TKey, TValue>> ToDictionaryAsync<TInput, TKey, TValue>( this IEnumerable<TInput> enumerable, Func<TInput, TKey> syncKeySelector, Func<TInput, Task<TValue>> asyncValueSelector) { Dictionary<TKey,TValue> dictionary = new Dictionary<TKey, TValue>(); foreach (var item in enumerable) { var key = syncKeySelector(item); var value = await asyncValueSelector(item); dictionary.Add(key,value); } return dictionary; } } 

You can use it as follows:

 private static async Task<Dictionary<int,string>> DoIt() { int[] numbers = new int[] { 1, 2, 3 }; return await numbers.ToDictionaryAsync( x => x, x => DoSomethingReturnString(x)); } 
+4
source share

If you call the asynchronous method, you can write a wrapper method that creates a new dictionary and creates a dictionary, iterating over each number, calling in turn DoSomethingReturnString :

 public async Task CallerAsync() { int[] numbers = new int[] { 1, 2, 3 }; Dictionary<int, string> dictionary = await ConvertToDictionaryAsync(numbers); } public async Task<Dictionary<int, string>> ConvertToDictionaryAsync(int[] numbers) { var dict = new Dictionary<int, string>(); for (int i = 0; i < numbers.Length; i++) { var n = numbers[i]; dict[n] = await DoSomethingReturnString(n); } return dict; } 
+3
source share

This is just a combination of @Yacoub and @David answers for an extension method that uses Task.WhenAll

 public static async Task<Dictionary<TKey, TValue>> ToDictionaryAsync<TInput, TKey, TValue>( this IEnumerable<TInput> enumerable, Func<TInput, TKey> syncKeySelector, Func<TInput, Task<TValue>> asyncValueSelector) { KeyValuePair<TKey, TValue>[] keyValuePairs = await Task.WhenAll( enumerable.Select(async input => new KeyValuePair<TKey, TValue>(syncKeySelector(input), await asyncValueSelector(input))) ); return keyValuePairs.ToDictionary(pair => pair.Key, pair => pair.Value); } 
0
source share

All Articles