How to hydrate a Dictionary with the results of async calls?

前端 未结 4 1285
梦毁少年i
梦毁少年i 2020-12-10 11:21

Suppose I have code that looks like this:

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


        
相关标签:
4条回答
  • 2020-12-10 11:34

    If calling from an asynchronous method, you can write a wrapper method that creates a new dictionary and builds a dictionary by iterating over each number, calling your DoSomethingReturnString in turn:

    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;
    }
    
    0 讨论(0)
  • 2020-12-10 11:42

    This is just a combination of @Yacoub's and @David's answers for an extension method which 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 讨论(0)
  • 2020-12-10 11:50

    If you insist on doing it with linq, Task.WhenAll is the key to "hydrate" 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);
    
    0 讨论(0)
  • 2020-12-10 11:56

    LINQ methods do not support asynchronous actions (e.g., asynchronous value selectors), but you can create one 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 like this:

    private static async Task<Dictionary<int,string>>  DoIt()
    {
        int[] numbers = new int[] { 1, 2, 3 };
    
        return await numbers.ToDictionaryAsync(
            x => x,
            x => DoSomethingReturnString(x));
    }
    
    0 讨论(0)
提交回复
热议问题