如何在 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&lt;IEnumerable&lt;T&gt;&gt;。但是为什么Select 会起作用,它肯定会抛出同样的错误吗?

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

有没有办法解决这个问题?

【问题讨论】:

你能提供Functions.GetDataAsync的声明吗? @Grundy Task&lt;IEnumerable&lt;T&gt;&gt;,但我已将完整声明添加到问题中。其中TmyEnumerable 的类型不同 @HimBromBeere,选择返回collection of collection,但我认为OP需要简单的collection 但是GetDataAsnyc 不返回枚举,而是返回Task 这是一个使用 linq-to-objects 很好地格式化我的代码的 HTTP 请求,不涉及数据层。 @AsadSaeeduddin 也许我的问题是一个糟糕的再现 - 这里有一个 IoC 容器正在发挥作用,将 Functions 注入我的班级。 myEnumerable 只是 IEnumable&lt;T&gt;,因为我知道我的业务逻辑是正确的,所以希望这无关紧要。 【参考方案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&lt;IEnumerable&lt;T&gt;&gt;。因此,您需要将它们全部触发,然后全部等待,然后通过 SelectMany 压缩结果。

【讨论】:

将其提取到方法中而不是将其内联到代码中的一个优点是扩展返回Task&lt;T&gt;,因此可以在代码中稍后的位置等待。 请注意,这会将所有对 OP 的 GetDataAsync(...) 的调用并行化。通常这是一件好事,但在某些情况下可能不需要。【参考方案2】:

异步 ​​lambda 表达式无法转换为简单的Func&lt;TSource, TResult&gt;

所以,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&lt;Task&lt;T&gt;&gt;,然后可以等待,例如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?的主要内容,如果未能解决你的问题,请参考以下文章

卡在 Rx Observable SelectMany

如何仅在 javascript 中执行相当于 LINQ SelectMany() 的操作

在实体框架核心中的 SelectMany + Select 之后“包含”不起作用

如何TBB获得150%LAMB的质押挖矿收益?

温故知新C# Linq中 Select && SelectMany 使用技巧

LinqToSQL Select 和 SelectMany vs Join [关闭]