Task.WhenAll的连续版本。

13

是否有一个非阻塞的Task.WaitAll,类似于Task.WhenAll,但不是并行的?

我写了这个,但也许它已经内置了吗?

public async Task<IEnumerable<T>> AwaitAllAsync<T>(IEnumerable<Task<T>> tasks)
{
    List<T> result = new List<T>();
    foreach(var task in tasks)
    {
        result.Add(await task);
    }

    return result;
}

我想知道在异步操作中是否有一种内置的等待所有任务完成的方法,但是按顺序执行。

考虑以下代码:

public class SaveFooCommandHandler : ICommandHandler<SaveFooCommand>
{
   private readonly IBusinessContext context;

   public SaveFooCommandHandler(IBusinessContext context)
   {
      this.context = context;
   }

   public async Task Handle(SaveFooCommand command)
   {
      var foos = (await Task.WhenAll(command.Foos.Select(foo => context.FindAsync<Foo>(foo.Id))).ToList()

      ...
   }
}

那将会失败,但是

var foos = await context.AwaitAllAsync(command.Foos.Select(foo => context.FindAsync<Foo>(foo.Id));

不会,context.FindAsyncdbcontext.Set<T>().FindAsync 的一个抽象。

你可以使用await context.Set<Foo>().Where(f => command.Foos.Contains(f.Id)).ToListAsync(),但这个例子是简化过的。


3
是的,它被称为Task.WhenAll - Yuval Itzchakov
1
它的并行处理与 EF 上下文不兼容,例如。 - Anders
1
但我不想这样做,我希望IO能够异步执行以释放资源,但我不想生成大量的上下文等。在这种情况下,我对并行性不感兴趣,只关注异步执行。 - Anders
1
当使用所有非线程安全的代码(如EF)时,将会失败,因此肯定不同于我的代码。 - Anders
1
@Anders:那你肯定是在做其他事情。给我们看看你的代码。 - SLaks
显示剩余11条评论
1个回答

22
我认为核心误解在于Task类型。在异步代码中,Task总是已经运行。因此,这是没有意义的:

是否有一种非阻塞的Task.WaitAll类似于Task.WhenAll但不是并行并发的?

如果您有一组任务,它们都已经启动

我想知道是否有一种内置的方法可以以异步但顺序的方式等待所有任务完成。

当然,您可以按顺序await它们。这种情况的标准模式是在foreach循环中使用await,就像您发布的方法一样。
但是,顺序await的唯一原因是您的LINQ查询是惰性评估的。特别是,如果您实体化任务集合,它将失败。所以这个有效:
var tasks = command.Foos.Select(foo => context.FindAsync<Foo>(foo.Id));
var foos = await context.AwaitAllAsync(tasks);

这个失败了:

var tasks = command.Foos.Select(foo => context.FindAsync<Foo>(foo.Id))
    .ToList();
var foos = await context.AwaitAllAsync(tasks);

在内部,Task.WhenAll 会实现您的任务序列,以便知道需要等待多少个任务。

但这其实不是重点。您要解决的真正问题是如何按顺序执行异步代码,最简单的方法是使用 foreach

var foos = new List<Foo>();
foreach (var fooId in command.Foos.Select(f => f.Id))
  foos.Add(await context.FindAsync<Foo>(fooId));

我希望找到一个适用于匿名类型的解决方案。 - Mick
你如何声明一个变量 var foos = new List<<Anonymous>> .... 我猜你可以只有一个对象列表,但这会使得操作 foos 变得相当困难。 - Mick
2
@Mick:如果你真的需要一个列表,那么你可以从一个通用方法中派生出类型,就像这样:async Task<List<T>> MyFunc<TId, T>(this IEnumerable<TId> ids, Func<TId, Task<T>> f) { var ret = new List<T>(); foreach (var id in ids) ret.Add(await f(id)); return ret; }。最难的部分是确定如何命名 MyFunc。它应该以 Async 结尾,并传达它是一个顺序的 await,而不是并发的 await 的含义。 - Stephen Cleary
var foos = await command.Foos.Select(f => f.Id).MyFunc(id => context.FindAsync<Foo>(id)); - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接