如何在 SelectMany 中使用异步 lambda?
Posted
技术标签:
【中文标题】如何在 SelectMany 中使用异步 lambda?【英文标题】:How to use async lambda with SelectMany? 【发布时间】:2015-11-03 09:32:58 【问题描述】:尝试在IEnumerable.SelectMany
中使用async
lambda 时出现以下错误:
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));
有没有办法解决这个问题?
【问题讨论】:
你能提供Functions.GetDataAsync
的声明吗?
@Grundy Task<IEnumerable<T>>
,但我已将完整声明添加到问题中。其中T
与myEnumerable
的类型不同
@HimBromBeere,选择返回collection of collection,但我认为OP需要简单的collection
但是GetDataAsnyc
不返回枚举,而是返回Task
。
这是一个使用 linq-to-objects 很好地格式化我的代码的 HTTP 请求,不涉及数据层。 @AsadSaeeduddin 也许我的问题是一个糟糕的再现 - 这里有一个 IoC 容器正在发挥作用,将 Functions
注入我的班级。 myEnumerable
只是 IEnumable<T>
,因为我知道我的业务逻辑是正确的,所以希望这无关紧要。
【参考方案1】:
这是一个扩展:
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 压缩结果。
【讨论】:
将其提取到方法中而不是将其内联到代码中的一个优点是扩展返回Task<T>
,因此可以在代码中稍后的位置等待。
请注意,这会将所有对 OP 的 GetDataAsync(...)
的调用并行化。通常这是一件好事,但在某些情况下可能不需要。【参考方案2】:
异步 lambda 表达式无法转换为简单的Func<TSource, TResult>
。
所以,select many 不能用。您可以在同步上下文中运行:
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
【讨论】:
非常感谢,我使用.Result
而不是await
使呼叫同步,一切都很好
投反对票可能是由于使用了.Result
而可能是cause deadlocks。【参考方案3】:
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);
【讨论】:
【参考方案4】:使用 C#8 和IAsyncEnumerable
,我们可以更自然地编写:
public static async IAsyncEnumerable<R>
SelectManyAsync<T, R>(this IEnumerable<T> ts, Func<T, Task<IEnumerable<R>>> func)
foreach (var t in ts)
var rs = await func(t);
foreach (var r in rs)
yield return r;
注意:
使用async foreach(...
迭代IAsyncEnumerable
【讨论】:
以上是关于如何在 SelectMany 中使用异步 lambda?的主要内容,如果未能解决你的问题,请参考以下文章
如何仅在 javascript 中执行相当于 LINQ SelectMany() 的操作
在实体框架核心中的 SelectMany + Select 之后“包含”不起作用