问题
I would like to create a generic method to await for a sequence of tasks to finish sequentially, retrieving the result of each one. This is the code I've created:
public static class TaskMixin
{
public static async Task<IEnumerable<T>> AwaitAll<T>(this IEnumerable<Task<T>> tasks)
{
var results = new List<T>();
foreach (var t in tasks)
{
results.Add(await t);
}
return results;
}
}
Is there a better or built-in way to do it?
Notice
Before writing the above method I tried with the built-in Task.WhenAll
method, but it caused me troubles because the tasks are using an Entity Framework's DbContext
and they seem to run concurrently.
This is the exception I got when I used Task.WhenAll
.
A second operation started on this context before a previous asynchronous operation completed
回答1:
There's no way of ensuring that the tasks passed to this method are not already running, in fact they probably are already started hot tasks. If the tasks are already running then your only awaiting them sequentially. If you want operations to run sequentially then you need to invoke the methods that return your tasks sequentially.
Or in the case of EF's DbContext
it's typically best to use a new DbContext
for each request. But if your doing many parallel queries then you might be approaching the problem the wrong way. Many parallel queries doesn't necessarily mean your queries are going to run faster.
You can however abstract away running operations sequentially by taking a Func<Task<T>>
delegate like this:
public async Task<IEnumerable<T>> RunSequentiallyAsync<T>(Func<Task<T>>[] operations)
{
var results = new List<T>();
foreach (var op in operations)
{
results.Add(await op());
}
return results;
}
回答2:
another way of awaiting the tasks sequentially is using IAsyncEnumerable and the await foreach syntax.
here is an sample extension method and a console app to use it. the console app reverses the tasks to demonstrate that the tasks are indeed called sequentially.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SO60066033
{
public class Program
{
static async Task Main(string[] args)
{
var someTasks = new Func<Task<int>>[]
{
() => Task.FromResult(1),
() => Task.FromResult(2),
() => Task.FromResult(3),
() => Task.FromResult(4),
};
var results = someTasks.AwaitAll();
await foreach (var result in results)
Console.WriteLine(result);
var reversedTasks = someTasks.Reverse().ToList();
var reversedResults = reversedTasks.AwaitAll();
await foreach (var result in reversedResults)
Console.WriteLine(result);
}
}
public static class TaskExtensions
{
public static async IAsyncEnumerable<T> AwaitAll<T>(this IEnumerable<Func<Task<T>>> tasks)
{
foreach (var task in tasks)
yield return await task();
}
}
}
来源:https://stackoverflow.com/questions/60066033/awaiting-a-sequence-of-tasks-to-run-sequentially