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
});
Where to define callback for Task based asynchronous method 1 Callback manually in my task delegate#N#public Task DoWorkAsync (DoWorkCompletedCallback completedCallback) { return... 2 Assign callback to task in task delegate#N#public Task DoWorkAsync (DoWorkCompletedCallback completedCallback) {... 3 Assign callback to task in caller More ...
For tasks, this is achieved through methods such as Task.ContinueWith. Language-based asynchronous support hides callbacks by allowing asynchronous operations to be awaited within normal control flow, and compiler-generated code provides this same API-level support.
Notify people that a task has finished. Trigger an authorization “When a task is completed” to move forward in the workflow. If you’re writing an article, for example, Flow can archive the working Folder and upload the article to your CMS when it’s finished. There are a lot of things that can happen if the task being completed is the trigger.
For performance reasons, if a task has already completed by the time the task is awaited, control is not yielded, and the function continues to execute. Additionally, returning to the original context isn't always the desired behavior and can be changed; this is described in more detail in the next section.
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);
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
}
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