Continuation tasks in .NET TPL: many tasks continued by a single task
March 18, 2014 Leave a comment
Tasks in .NET TPL make it easy to assign tasks that should run upon the completion of a certain task.
We’ll need a basic object with a single property:
public class Series { public int CurrentValue { get; set; } }
In previous installments of the posts on TPL we saw how to create tasks that are scheduled to run after a single task has completed: check here and here. It is also possible to create many tasks and a single continuation task that runs after all the antecedents have completed. The ContinueWhenAll() method takes an array or Task objects, all of which have to complete before the continuation task can proceed.
We create 10 tasks each of which increases the its local series value by 1000 in total in a loop:
Series series = new Series(); Task<int>[] motherTasks = new Task<int>[10]; for (int i = 0; i < 10; i++) { motherTasks[i] = Task.Factory.StartNew<int>((stateObject) => { int seriesValue = (int)stateObject; for (int j = 0; j < 1000; j++) { seriesValue++; } return seriesValue; }, series.CurrentValue); }
All 10 tasks are antecedent tasks in the chain. Let’s declare the continuation task:
Task continuation = Task.Factory.ContinueWhenAll<int>(motherTasks, antecedents => { foreach (Task<int> task in antecedents) { series.CurrentValue += task.Result; } }); continuation.Wait(); Console.WriteLine(series.CurrentValue);
We extract the series value from each antecedent task and increase the overall series value by the individual task results, i.e. a 1000. The lambda expression looks awkward a bit: the array of antecedent tasks appears twice. Once as the first argument to the ContinueWhenAll method and then as an input to the lambda expression.
There’s one more thing to note about the syntax: ContinueWhenAll of T denotes the return type of the antecedent tasks. You can also specify the return type of the continuation. Here’s a list of possibilities:
The continuation and the antecedent tasks both return an int:
Task<int> continuation = Task.Factory.ContinueWhenAll<int>(motherTasks, antecedents => { foreach (Task<int> task in antecedents) { series.CurrentValue += task.Result; } return 1234; });
Continuation is void and antecedent tasks return an int:
Task continuation = Task.Factory.ContinueWhenAll<int>(motherTasks, antecedents => { foreach (Task<int> task in antecedents) { series.CurrentValue += task.Result; } });
All of them are void:
Task continuation = Task.Factory.ContinueWhenAll(motherTasks, antecedents => { foreach (Task<int> task in antecedents) { //do something } });
Continuation returns an int and antecedent tasks are void:
Task<int> continuation = Task<int>.Factory.ContinueWhenAll(motherTasks, antecedents => { foreach (Task<int> task in antecedents) { //do something } return 1234; });
Note that it’s enough to start the antecedent tasks, which we did with Task.Factory.StartNew. The continuation task will be scheduled automatically.
View the list of posts on the Task Parallel Library here.