我有一个我创建的任务列表:
public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
var foos = await GetFoosAsync();
var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
...
}
使用.ToList()
,任务都应该开始。现在我想等待他们完成并返回结果。
这适用于上述...
块:
var list = new List<Foo>();
foreach (var task in tasks)
list.Add(await task);
return list;
它做我想要的,但这看起来很笨拙。我宁愿写一些更简单的东西:
return tasks.Select(async task => await task).ToList();
...但这不会编译。我错过了什么?或者以这种方式表达事情是不可能的?
答案 0 :(得分:119)
LINQ与async
代码无法完美配合,但您可以这样做:
var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);
如果您的任务都返回相同类型的值,那么您甚至可以这样做:
var results = await Task.WhenAll(tasks);
非常好。 WhenAll
返回一个数组,所以我相信你的方法可以直接返回结果:
return await Task.WhenAll(tasks);
答案 1 :(得分:6)
为了扩展Stephen的回答,我创建了以下扩展方法来保留LINQ的fluent style。然后你可以做
await someTasks.WhenAll()
namespace System.Linq
{
public static class IEnumerableExtensions
{
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source)
{
return Task.WhenAll(source);
}
}
}
答案 2 :(得分:2)
Task.WhenAll的一个问题是它会创建并行性。在大多数情况下,它可能会更好,但有时你想避免它。例如,从DB中批量读取数据并将数据发送到某些远程Web服务。您不希望将所有批次加载到内存中,但是一旦处理完上一批,就会命中DB。所以,你必须打破异步性。这是一个例子:
var events = Enumerable.Range(0, totalCount/ batchSize)
.Select(x => x*batchSize)
.Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult())
.SelectMany(x => x);
foreach (var carEvent in events)
{
}
注意.GetAwaiter()。GetResult()将其转换为同步。只有处理了一批batchSize事件后,DB才会被懒惰地命中。
答案 3 :(得分:1)
使用Task.WaitAll
或Task.WhenAll
为准。
答案 4 :(得分:0)
Task.WhenAll应该在这里做。