2015-11-03 19 views
10

Sto ottenendo il seguente errore quando si tenta di utilizzare un async lambda all'interno IEnumerable.SelectMany:Come usare lambda async con SelectMany?

var result = myEnumerable.SelectMany(async (c) => await Functions.GetDataAsync(c.Id)); 

Gli argomenti di tipo per il metodo 'IEnumerable System.Linq.Enumerable.SelectMany (questo IEnumerable, Func>) "non può essere dedotto dall'utilizzo. Prova a specificare gli argomenti di tipo esplicitamente

Dove GetDataAsync è definito come:

public interface IFunctions { 
    Task<IEnumerable<DataItem>> GetDataAsync(string itemId); 
} 

public class Functions : IFunctions { 
    public async Task<IEnumerable<DataItem>> GetDataAsync(string itemId) { 
     // return await httpCall(); 
    } 
} 

Credo perché il mio metodo GetDataAsync in realtà restituisce un Task<IEnumerable<T>>. Ma perché funziona Select, sicuramente dovrebbe generare lo stesso errore?

var result = myEnumerable.Select(async (c) => await Functions.GetDataAsync(c.Id)); 

C'è un modo per aggirare questo?

+1

può offrire dichiarazione per 'Functions.GetDataAsync'? – Grundy

+0

@Grundy 'Attività >', ma ho aggiunto la dichiarazione completa alla domanda. Dove 'T' è diverso dal tipo di' myEnumerable' – CodingIntrigue

+1

@HimBromBeere, selezionare return _collection of collection_, ma penso che OP abbia bisogno di una semplice raccolta – Grundy

risposta

10

l'espressione asincrona lambda non può essere convertita in semplice Func<TSource, TResult>.

Quindi, selezionare molti non possono essere utilizzati. È possibile eseguire in un contesto sincronizzato:

myEnumerable.Select(c => Functions.GetDataAsync(c.Id)).SelectMany(task => task.Result); 

o

List<DataItem> result = new List<DataItem>(); 

foreach (var ele in myEnumerable) 
{ 
    result.AddRange(await Functions.GetDataAsyncDo(ele.Id)); 
} 

Non è possibile né uso yield return - è legato alla progettazione. f.e .:

public async Task<IEnuemrable<DataItem>> Do() 
{ 
    ... 
    foreach (var ele in await Functions.GetDataAsyncDo(ele.Id)) 
    { 
     yield return ele; // compile time error, async method 
          // cannot be used with yield return 
    } 

} 
+0

Grazie mille, ho usato '.Result' invece di' await' per rendere la chiamata sincrona e tutto va bene – CodingIntrigue

+0

Qualche commento su downvote? – pwas

+1

Il downvote era probabilmente dovuto all'uso di ".Result" che potrebbe [causare deadlock] (https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). –

14

Si tratta di un'estensione:

public static async Task<IEnumerable<T1>> SelectManyAsync<T, T1>(this IEnumerable<T> enumeration, Func<T, Task<IEnumerable<T1>>> func) 
{ 
    return (await Task.WhenAll(enumeration.Select(func))).SelectMany(s => s); 
} 

che permette di eseguire:

var result = await myEnumerable.SelectManyAsync(c => Functions.GetDataAsync(c.Id)); 

Spiegazione: si dispone di un elenco di attività, ognuno ritorna Task<IEnumerable<T>>. Quindi è necessario licenziarli tutti, quindi attendere tutti e quindi schiacciare il risultato tramite SelectMany.

+0

Un vantaggio di averlo estratto in un metodo, piuttosto che averlo inserito nel codice, è che l'estensione restituisce un "Compito ", quindi questo può essere atteso in un secondo momento nel codice. – Tim

+0

Attenzione, questo parallelizza tutte le chiamate all'OP 'GetDataAsync (...)'. Di solito è una buona cosa, ma potrebbe non essere desiderato in determinati scenari. –

2

Select funziona perché restituirà un IEnumerable<Task<T>>, che può quindi essere atteso con ad es. Task.WhenAll.

Quindi, una soluzione semplice a questo problema è:

IEnumerable<Task<IEnumerable<T>>> tasks = source.Select(GetNestedEnumerableTask); 
IEnumerable<T>[] nestedResults = await Task.WhenAll(tasks); 
IEnumerable<T> results = nestedResults.SelectMany(nr => nr);