2016-06-13 22 views
7

Supponiamo che io sono il codice che assomiglia a questo:Come idratare un dizionario con i risultati delle chiamate asincrone?

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

Supponiamo che io voglia creare un dizionario che contiene il risultato della chiamata DoSomethingReturnString per ogni numero simile a questo:

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

che ha vinto' funziona perché DoSomethingReturnString restituisce Task<string> anziché string. L'intellisense mi ha suggerito di provare a specificare la mia espressione lambda come asincrona, ma anche questo non sembra aver risolto il problema.

+1

'DoSomethingReturnString (n) .Result', ma poi si sta bloccando. Se non è quello che stai per, allora avresti bisogno di una funzione asincrona che restituisca un 'Compito ' –

+2

Questo è un ottimo esempio del perché "async fino in fondo" è un principio guida quando si lavora con codice asincrono. –

+0

Anche il codice di consumo è asincrono? –

risposta

1

Se si chiama da un metodo asincrono, è possibile scrivere un metodo wrapper che crea un nuovo dizionario e costruisce un dizionario per iterare su ogni numero, chiamare il tuo DoSomethingReturnString a sua volta:

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

Non è possibile farlo senza utilizzare un ciclo? Usare un'espressione lambda mi sembra più pulito. –

+0

Il lambda verrà iterato internamente così com'è. Lo zucchero sintattico della lambda nel tuo caso sta effettivamente intralciando la giusta attesa. È certamente possibile, ma potrebbe anche danneggiare la tua leggibilità. –

+0

Inoltre, un approccio LINQ può anche finire per iterare due volte a seconda dell'uso e della struttura ... una volta per selezionare e un'altra per creare il dizionario. Questo approccio sarà sempre iterare solo una volta. –

4

Se ti ostini a fare con LINQ, Task.WhenAll è la chiave per "idratare" dizionario:

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

Cosa c'entra questo con la pressione del GC? –

1

metodi di LINQ non supportano le azioni asincrona (ad esempio, selettori di valore asincroni), ma è possibile creare uno voi stessi. Ecco un riutilizzabile ToDictionaryAsync metodo di estensione che supporta un selettore di valore asincrona:

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

è possibile utilizzarlo in questo modo:

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

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