2015-11-03 2 views
10

Я получаю следующее сообщение об ошибке при попытке использовать async лямбда в IEnumerable.SelectMany:Как использовать асинхронную лямбду с помощью SelectMany?

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

аргументы типа для метода «IEnumerable System.Linq.Enumerable.SelectMany (это IEnumerable, Func>) 'не может быть выведено из использования. Попробуйте указать аргументы типа явно

Где GetDataAsync определяется как:

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

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

Я предполагаю, потому что мой GetDataAsync метод фактически возвращает Task<IEnumerable<T>>. Но почему работает Select, наверняка он должен бросить ту же ошибку?

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

Есть ли способ обойти это?

+1

вы можете предоставить заявление на 'Functions.GetDataAsync'? – Grundy

+0

@Grundy 'Задача >', но я добавил полную декларацию к вопросу. Где 'T' отличается от типа' myEnumerable' – CodingIntrigue

+1

@HimBromBeere, выберите return _collection collection_, но я думаю, что OP нужна простая коллекция – Grundy

ответ

10

выражение async лямбда не может быть преобразовано в простой Func<TSource, TResult>.

Таким образом, многие из них не могут быть использованы. Вы можете работать в синхронном контексте:

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

или

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

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

Вы не можете ни использовать yield return - это дизайн. 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

Большое спасибо, я использовал '.Result' вместо' await', чтобы сделать вызов синхронным, и все хорошо – CodingIntrigue

+0

Любые комментарии к downvote? – pwas

+1

Возможно, Downvote был вызван использованием '.Result', который может [вызвать блокировки] (https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). –

14

Это расширение:

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

Это позволяет запускать:

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

Объяснение: у вас есть список задач, каждая из которых возвращает Task<IEnumerable<T>>. Поэтому вам нужно уволить их всех, а затем ждать, а затем раздавать результат через SelectMany.

+0

Одно из преимуществ использования этого метода, а не его встроенного в ваш код, заключается в том, что расширение возвращает «Задача », поэтому это можно ожидать в более позднем месте в коде. – Tim

+0

Имейте в виду, что это распараллеливает все вызовы OP 'GetDataAsync (...)'. Обычно это хорошо, но может быть нежелательно в определенных сценариях. –

2

Select работает, потому что он вернет IEnumerable<Task<T>>, который затем можно ожидать, например. Task.WhenAll.

Таким образом, простое решение этой проблемы:

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