问题
Suppose I have the following:
IEnumerable<Task<TimeSpan>> tasks = //...
TimeSpan[] results = await Task.WhenAll(tasks);
// Handle results
By the time I can handle the results all the task must have finished.
Is there a way to handle each result on demand?
Like registering a delegate / callback that will get executed when a task is completed:
IEnumerable<Task<TimeSpan>> tasks = //...
await Task.WhenAll(tasks, result =>
{
// A task has finished. This will get executed.
// result is of type TimeSpan
});
回答1:
Is there a way to handle each result on demand?
Yes, you use WhenAny
instead of WhenAll
... or call ContinueWith
on each task.
For example, for the WhenAny
approach:
ISet<Task<TimeSpan>> tasks = new HashSet<Task<TimeSpan>>(...);
while (tasks.Count != 0)
{
var task = await Task.WhenAny(tasks);
// Use task here
tasks.Remove(task);
}
There's another option you could use, where you transform the original sequence of tasks into a sequence of tasks which completes in order, but giving the same results. Details are in this blog post, but the result is that you can use:
foreach (var task in tasks.InCompletionOrder())
{
var result = await task;
// Use the result
}
回答2:
Is there a way to handle each result on demand?
Like registering a delegate / callback that will get executed when a task is completed
Yes, you just have to adjust your thinking a bit.
Forget registering callbacks (ContinueWith is a dangerous, extremely low-level API). Also, you almost never have to order tasks by completion. Instead, think about your problem in terms of operations (tasks).
Right now, you have a collection of tasks that return TimeSpan
. Each item in that collection is a single operation that returns TimeSpan
. What you really want to do is introduce the concept of a single higher-level operation that waits for the original operation to complete and then executes your post-operation logic.
This is exactly what async
/await
is for:
private static async Task<TimeSpan> HandleResultAsync(Task<TimeSpan> operation)
{
var result = await operation;
// A task has finished. This will get executed.
// result is of type TimeSpan
...
return result; // (assuming you want to propagate the result)
}
Now, you want to apply this higher-level operation to your existing operations. LINQ's Select
is perfect for this:
IEnumerable<Task<TimeSpan>> tasks = ...
IEnumerable<Task<TimeSpan>> higherLevelTasks = tasks.Select(HandleResultAsync);
TimeSpan[] results = await Task.WhenAll(higherLevelTasks);
// By the time you get here, all results have been handled individually.
If you don't need the final collection of results, this can be further simplified:
private static async Task HandleResultAsync(Task<TimeSpan> operation)
{
var result = await operation;
// A task has finished. This will get executed.
// result is of type TimeSpan
...
}
IEnumerable<Task<TimeSpan>> tasks = ...
IEnumerable<Task> higherLevelTasks = tasks.Select(HandleResultAsync);
await Task.WhenAll(higherLevelTasks);
来源:https://stackoverflow.com/questions/39624386/is-there-a-callback-for-when-a-task-is-completed-in-task-whenall