2016-06-13 14 views
7

Załóżmy, że mam kodu, który wygląda tak:Jak nawodnić słownik z wynikami połączeń asynchronicznych?

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

Załóżmy, że chcę utworzyć słownik, który zawiera wynik wywołania DoSomethingReturnString dla każdego numeru podobnego do tego:

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

który wygrał” t działa, ponieważ funkcja DoSomethingReturnString zwraca wartość Task<string> zamiast string. Intellisense zasugerował, żebym spróbował określić moje wyrażenie lambda jako asynchroniczne, ale to też nie rozwiązało problemu.

+1

'DoSomethingReturnString (n) .Result', ale wtedy jest to blokowanie. Jeśli nie o to chodzi, to potrzebujesz funkcji asynchronicznej, która zwraca 'Zadanie >' –

+2

Jest to świetny przykład, dlaczego "asynchronizacja w dół" jest zasadą przewodnią podczas pracy z kodem asynchronicznym. –

+0

Czy zażywający kod jest również asynchroniczny? –

Odpowiedz

1

Jeśli dzwonisz z metody asynchronicznego, można napisać metodę otoki, który tworzy nowy słownik i buduje słownika przez Iterowanie nad każdym numerem, nazywając swój DoSomethingReturnString kolejno:

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

Czy nie można tego zrobić bez użycia pętli? Użycie wyrażenia lambda wydaje mi się czystsze. –

+0

Lambda będzie iterować wewnętrznie tak jak jest. Syntaktyczny cukier lambda w twoim przypadku faktycznie przeszkadza w prawidłowym oczekiwaniu. Jest to z pewnością możliwe, ale może również zranić Twoją czytelność. –

+0

Ponadto, podejście LINQ może również zakończyć iteracje dwa razy w zależności od użycia i struktury ... raz, aby wybrać i innym razem, aby utworzyć słownik. To podejście będzie zawsze powtarzać tylko raz. –

4

Jeśli domagać się robi to z LINQ, Task.WhenAll jest kluczem do „hydrat” w słowniku:

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

Co to ma wspólnego z ciśnieniem GC? –

1

metody LINQ nie wspierać działania asynchronicznych (np selektorów wartości asynchroniczny), ale można tworzyć samemu. Oto wielokrotnego użytku ToDictionaryAsync metodę rozszerzenia, które obsługuje selektor wartość asynchronicznego:

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; 
    } 
} 

Można go używać tak:

private static async Task<Dictionary<int,string>> DoIt() 
{ 
    int[] numbers = new int[] { 1, 2, 3 }; 

    return await numbers.ToDictionaryAsync(
     x => x, 
     x => DoSomethingReturnString(x)); 
}