有没有办法结合LINQ和async

use*_*000 5 .net c# 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;
}
Run Code Online (Sandbox Code Playgroud)

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

Joh*_* Wu 7

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

创建:

var query = list.Select( a => a.Name);
Run Code Online (Sandbox Code Playgroud)

这些调用始终是同步的.但是这个代码除了创建一个公开IEnumerable的对象之外没有什么作用.由于称为延迟执行的模式,实际工作直到稍后才会完成.

迭代:

var results = query.ToList();
Run Code Online (Sandbox Code Playgroud)

此代码获取可枚举并获取每个项的值,这通常涉及调用您的回调委托(在本例中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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

var results = await inputs.Select( async i => await InnerMethodAsync(i) ).ToListAsync();
Run Code Online (Sandbox Code Playgroud)

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

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

试一试,看看我在DotNetFiddle的演示中的时间.

  • 请注意,*ToListAsync* 用于`IQueryable&lt;&gt;`,而不是`IEnumerable&lt;&gt;`。 (3认同)
  • @xanatos确实.另外一般不适用,因为来自`EntityFramework.dll` (2认同)