Is there a nonblocking Task.WaitAll
similar to Task.WhenAll
, but not parallel?
I wrote this, but maybe it’s built-in?
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;
}
I want to know if there is a built-in way of waiting for all tasks to complete in async, but a sequential way.
Consider this code:
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()
...
}
}
That will fail, but
var foos = await context.AwaitAllAsync(command.Foos.Select(foo => context.FindAsync<Foo>(foo.Id));
will not, context.FindAsync
is an abstraction of dbcontext.Set<T>().FindAsync
You could do await context.Set<Foo>().Where(f => command.Foos.Contains(f.Id)).ToListAsync()
, but the example is simplified.
I think the core misunderstanding is around the Task
type. In asynchronous code, a Task
is always already running. So this doesn't make sense:
Is there a non-blocking
Task.WaitAll
similar toTask.WhenAll
but notparallelconcurrent?
If you have a collection of tasks, they're all already started.
I want to know if there is a build in way of waiting for all tasks to complete in async but sequential way.
You can, of course, await
them sequentially. The standard pattern for this is to use await
inside a foreach
loop, just like the method you posted.
However, the only reason the sequential-await
works is because your LINQ query is lazily evaluated. In particular, if you reify your task collection, it will fail. So this works:
var tasks = command.Foos.Select(foo => context.FindAsync<Foo>(foo.Id));
var foos = await context.AwaitAllAsync(tasks);
and this fails:
var tasks = command.Foos.Select(foo => context.FindAsync<Foo>(foo.Id))
.ToList();
var foos = await context.AwaitAllAsync(tasks);
Internally, Task.WhenAll
reifies your task sequence so it knows how many tasks it needs to wait for.
But this is really beside the point. The real problem you're trying to solve is how to serially execute asynchronous code, which is most easily done using foreach
:
var foos = new List<Foo>();
foreach (var fooId in command.Foos.Select(f => f.Id))
foos.Add(await context.FindAsync<Foo>(fooId));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With