基本上我有一个像
这样的程序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查询,就像一个“异步流”(我可以描述它的最佳方式)。
答案 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)
一个相当明显的答案,但你已经一起使用了LINQ
和async
- 你正在使用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;
添加到您的代码中。