有没有办法结合LINQ和async

时间:2018-06-01 05:28:13

标签: c# .net linq asynchronous async-await

基本上我有一个像

这样的程序
var results = await Task.WhenAll(
    from input in inputs
    select Task.Run(async () => await InnerMethodAsync(input))
);
.
.
.
private static async Task<Output> InnerMethodAsync(Input input)
{
    var x = await Foo(input);
    var y = await Bar(x);
    var z = await Baz(y);
    return z;
}

我想知道是否有一种奇特的方式将它组合成一个LINQ查询,就像一个“异步流”(我可以描述它的最佳方式)。

3 个答案:

答案 0 :(得分:7)

使用LINQ时,通常有两个部分:创建和迭代。

创建:

var query = list.Select( a => a.Name);

这些调用始终是同步的。但是这个代码除了创建一个公开IEnumerable的对象之外没有什么作用。由于名为deferred execution的模式,实际工作要到晚些时候才能完成。

迭代:

var results = query.ToList();

此代码获取可枚举并获取每个项的值,这通常涉及调用回调委托(在本例中为a => a.Name)。这是可能昂贵的部分,并且可以从非同步性中受益,例如,如果您的回调类似于async a => await httpClient.GetByteArrayAsync(a)

所以这是我们感兴趣的迭代部分,如果我们想让它异步。

这里的问题是ToList()(以及强制迭代的大多数其他方法,如Any()Last())不是异步方法,因此将同步调用您的回调委托,你最终会得到一个任务列表,而不是你想要的数据。

我们可以使用这样的代码来解决这个问题:

public static class ExtensionMethods
{
    static public async Task<List<T>> ToListAsync<T>(this IEnumerable<Task<T>> This)
    {
        var tasks = This.ToList();     //Force LINQ to iterate and create all the tasks. Tasks always start when created.
        var results = new List<T>();   //Create a list to hold the results (not the tasks)
        foreach (var item in tasks)
        {
            results.Add(await item);   //Await the result for each task and add to results list
        }
        return results;
    }
}

使用此扩展方法,我们可以重写您的代码:

var results = await inputs.Select( async i => await InnerMethodAsync(i) ).ToListAsync();

^这应该为您提供您正在寻找的异步行为,并避免创建线程池任务,如您的示例所示。

注意:如果您正在使用LINQ到实体,那么昂贵的部分(数据检索)不会向您公开。对于LINQ到实体,您需要使用EF框架附带的ToListAsync()

尝试一下,看看DotNetFiddle上我的演示中的时间安排。

答案 1 :(得分:2)

一个相当明显的答案,但你已经一起使用了LINQasync - 你正在使用LINQ的select进行投影,并开始一堆异步任务,然后在结果上await,它提供异步并行模式。

虽然您可能只是提供了一个示例,但您的代码中有几点需要注意(我已经切换到Lambda语法,但适用相同的主体)

  • 由于在第一个await之前每个任务的CPU绑定工作基本上为零(即var x = await Foo(input);之前没有完成工作),因此no real reason在这里使用Task.Run
  • 由于在调用InnerMethodAsync后无法在lambda中完成任务,因此您无需将InnerMethodAsync调用包含在async lambda中(但要警惕{ {1}})

即。您只需选择IDisposable返回的Task,然后点击InnerMethodAsync即可。

Task.WhenAll

使用asynchronony和Linq可以实现更复杂的模式,但是不应该重新发明轮子,你应该看看Reactive Extensions, and the TPL Data Flow Library,它有许多用于复杂流程的构建块。

答案 2 :(得分:2)

尝试使用Microsoft的Reactive Framework。然后你可以这样做:

IObservable<Output[]> query =
    from input in inputs.ToObservable()
    from x in Observable.FromAsync(() => Foo(input))
    from y in Observable.FromAsync(() => Bar(x))
    from z in Observable.FromAsync(() => Baz(y))
    select z;

Output[] results = await query.ToArray();

简单。

只需NuGet“System.Reactive”并将using System.Reactive.Linq;添加到您的代码中。