Is there a callback for when a task is completed in Task.WhenAll

大城市里の小女人 提交于 2019-12-23 09:01:46

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!