Continuation tasks in .NET TPL: many tasks continued by a single task

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.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: