如何使用LINQ异步等待任务列表?

时间:2014-02-18 23:52:35

标签: c# linq async-await

我有一个我创建的任务列表:

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();

...但这不会编译。我错过了什么?或者以这种方式表达事情是不可能的?

5 个答案:

答案 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.WaitAllTask.WhenAll为准。

答案 4 :(得分:0)

Task.WhenAll应该在这里做。