Continuation tasks in .NET TPL: make continuation conditional

Tasks in .NET TPL make it easy to assign tasks that should run upon the completion of a certain task. You can also specify on what condition the continuation task should run.

We’ll need a basic object with a single property:

public class Series
{
	public int CurrentValue
	{
		get;
		set;
	}
}

Declare and start a task that increases the CurrentValue in a loop and return the Series. This task is called the antecedent task in a chain of tasks.

Task<Series> motherTask = Task.Factory.StartNew<Series>(() =>
{
	Series series = new Series();
	for (int i = 0; i < 5000; i++)
	{
		series.CurrentValue++;
	}
	return series;
});

You can create other tasks that will continue upon the completion of the antecedent task. The default value is that the continuation task will be scheduled to start when the preceding task has completed. However, the Task constructor – and the task factory – let’s you supply a TaskContinuationOptions enumeration.

This is how you declare a default continuation task:

Task<int> firstContinuationTask
	= motherTask.ContinueWith<int>((Task<Series> antecedent) =>
	{
		Console.WriteLine("First continuation task reporting series value: {0}", antecedent.Result.CurrentValue);
		return antecedent.Result.CurrentValue * 2;
	});

The first continuation task reads the Series object built by the antecedent, the “mother” task. It prints out the CurrentValue of the Series. The continuation task also returns an integer: the series value multiplied by two.

You can extend this declaration as follows:

Task<int> firstContinuationTask
	= motherTask.ContinueWith<int>((Task<Series> antecedent) =>
	{
		Console.WriteLine("First continuation task reporting series value: {0}", antecedent.Result.CurrentValue);
		return antecedent.Result.CurrentValue * 2;
	}, TaskContinuationOptions.OnlyOnRanToCompletion);

Note the extra TaskContinuationOptions.OnlyOnRanToCompletion parameter. As the name implies it will only run if the antecedent has completed successfully: it has not been cancelled and has not thrown an unhandled exception.

Other important enumeration values:

  • None: the default value, the continuation will run when the antecedent completes
  • NotOnRanToCompletion: the opposite of OnlyOnRanToCompletion, i.e. only run if antecedent has been cancelled or has thrown an unhandled exception
  • OnlyOnFaulted: if antecedent has thrown an unhandled exception
  • NotOnFaulted: if antecedent has not thrown any unhandled exceptions
  • OnlyOnCancelled: if antecedent has been cancelled
  • NotOnCancelled: if antecedent has not been cancelled

Let’s declare a second continuation task:

Task secondContinuationTask
	= firstContinuationTask.ContinueWith((Task<int> antecedent) =>
	{
		Console.WriteLine("Second continuation task reporting series value: {0}", antecedent.Result);
	}, TaskContinuationOptions.NotOnRanToCompletion);

Note that it’s enough to start the first task, which we did with Task.Factory.StartNew. The continuation tasks will be scheduled automatically.

Provided that the mother task completes and the first continuation also completes the second continuation will not run due to the specified NotOnRanToCompletion option.

View the list of posts on the Task Parallel Library here.

Continuation tasks in .NET TPL: continuation with many tasks

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;
	}
}

Declare and start a task that increases the CurrentValue in a loop and return the Series. This task is called the antecedent task in a chain of tasks.

Task<Series> motherTask = Task.Factory.StartNew<Series>(() =>
{
	Series series = new Series();
	for (int i = 0; i < 5000; i++)
	{
		series.CurrentValue++;
	}
	return series;
});

So this simple task returns a Series object with a current value property.

You can create other tasks that will continue upon the completion of the antecedent task. You can actually specify a condition when the continuation task should start. The default value is that the continuation task will be scheduled to start when the preceding task has completed. See the next post on TPL for examples.

You can combine continuations in different ways. You can have a linear chain:

  1. First task completes
  2. Second task follows first task and completes
  3. Third task follows second task and completes
  4. etc. with task #4, #5…

…or

  1. First task completes
  2. Second and thirds tasks follow first task and they all run
  3. Fourth task starts after second task and completes
  4. etc. with task #6, #7…

So you can create a whole tree of continuations as you wish. You can build up each continuation task one by one. Let’s declare the first continuation task:

Task<int> firstContinuationTask
	= motherTask.ContinueWith<int>((Task<Series> antecedent) =>
	{
		Console.WriteLine("First continuation task reporting series value: {0}", antecedent.Result.CurrentValue);
		return antecedent.Result.CurrentValue * 2;
	});

The first continuation task reads the Series object built by the antecedent, the “mother” task. It prints out the CurrentValue of the Series. The continuation task also returns an integer: the series value multiplied by two.

We’ll let a second continuation task print out the integer returned by the first continuation task. I.e. from the point view of the second continuation task the first continuation task is the antecedent. We can say that “motherTask” is the “grandma” 🙂

Task secondContinuationTask
	= firstContinuationTask.ContinueWith((Task<int> antecedent) =>
	{
		Console.WriteLine("Second continuation task reporting series value: {0}", antecedent.Result);
	});

Note that it’s enough to start the first task, which we did with Task.Factory.StartNew. The continuation tasks will be scheduled automatically.

Alternatively you can declare the continuation tasks in a chained manner:

motherTask.ContinueWith<int>((Task<Series> antecedent) =>
{
	Console.WriteLine("First continuation task reporting series value: {0}", antecedent.Result.CurrentValue);
	return antecedent.Result.CurrentValue * 2;
}).ContinueWith((Task<int> antecedent) =>
{
	Console.WriteLine("Second continuation task reporting series value: {0}", antecedent.Result);
});

View the list of posts on the Task Parallel Library here.

Continuation tasks in .NET TPL: continuation with different result type

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;
	}
}

Declare a task that increases the CurrentValue in a loop and return the Series. This task is called the antecedent task.

Task<Series> motherTask = Task.Factory.StartNew<Series>(() =>
{
	Series series = new Series();
	for (int i = 0; i < 10000; i++)
	{
		series.CurrentValue++;
	}
	return series;
});

Declare the continuation task where we also use the antecedent as method parameter. Note that the return type of the continuation task can be different from that of the antecedent:

Task continuationTask = motherTask.ContinueWith((Task previousTask) =>
{
	Console.WriteLine("Interim value: {0}", previousTask.Result.CurrentValue);
	return previousTask.Result.CurrentValue * 2;
});

Read the result of the continuation task:

Console.WriteLine("Final value: {0}", continuationTask.Result);

View the list of posts on the Task Parallel Library here.

Waiting for one in a group of Tasks to complete in .NET C#

You can use the static WaitAny() method to wait for one of many tasks to complete. The method blocks until one of the tasks has finished. It returns the array index of the task that has finished first.

WaitAny() has a number of overloaded versions which follow the same pattern as the Wait() method we saw in this post. If you use one of the overloaded methods then an array index of -1 means that the time period has expired or the cancellation token has been cancelled before any of the tasks could complete.

Example:

Build the cancellation token:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

Construct two – or more- tasks:

Task taskOne = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 10; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
		cancellationToken.WaitHandle.WaitOne(1000);
	}
	Console.WriteLine("Task 1 complete");
}, cancellationToken);

Task taskTwo = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 5; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
		cancellationToken.WaitHandle.WaitOne(500);
	}
	Console.WriteLine("Task 2 complete");
}, cancellationToken);

Wait for the first one:

int arrayIndex = Task.WaitAny(taskOne, taskTwo);

Note that a task provided to the WaitAny method is considered “complete” if either of the following is true:

  • The task finishes its work
  • The task is cancelled
  • The task has thrown an exception

If one of the tasks throws an exception then WaitAny will also throw one.

View the list of posts on the Task Parallel Library here.

Waiting for a group of Tasks to complete in .NET C#

You can use the static WaitAll() method to wait for more than one task to complete. If you use the WaitAll(tasks[]) version then WaitAll only returns if all tasks in the task array have completed.

WaitAll() has a number of overloaded versions which follow the same pattern as the Wait() method we saw in this post.

Example:

Build the cancellation token:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

Construct two – or more- tasks:

Task taskOne = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 10; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
		cancellationToken.WaitHandle.WaitOne(1000);
	}
	Console.WriteLine("Task 1 complete");
}, cancellationToken);

Task taskTwo = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 5; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
		cancellationToken.WaitHandle.WaitOne(500);
	}
	Console.WriteLine("Task 2 complete");
}, cancellationToken);

Wait for them:

Task.WaitAll(taskOne, taskTwo);

Note that a task provided to the WaitAll method is considered “complete” if either of the following is true:

  • The task finishes its work
  • The task is cancelled
  • The task has thrown an exception

If one of the tasks throws an exception then WaitAll will also throw one.

View the list of posts on the Task Parallel Library here.

Wait for a single Task in .NET C#: the Wait() method

Waiting for a single Task to finish is as easy as calling the Wait() method or one of its overloaded versions:

Wait();
Wait(CancellationToken);
Wait(int);
Wait(TimeSpan);
Wait(int, CancellationToken);

In short from top to bottom:

  • Wait until the Task completes, is cancelled or throws an exception
  • Wait until the cancellation token is cancelled or the task completes, is cancelled or throws an exception
  • Wait for ‘int’ amount of milliseconds to pass OR for the task to complete, be cancelled or throw an exception, whichever happens first
  • Wait for ‘TimeSpan’ amount of time to pass OR for the task to complete, be cancelled or throw an exception, whichever happens first
  • Wait for ‘int’ amount of milliseconds to pass, for the cancellation token to be cancelled, OR for the task to complete, be cancelled or throw an exception, whichever happens first

Example:

Construct the cancellation token:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

Start a task:

Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 10; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
		cancellationToken.WaitHandle.WaitOne(1000);
	}
}, cancellationToken);

We can then wait for the task to complete:

task.Wait();

If we are impatient then we specify a maximum amount of milliseconds for the task to complete otherwise it is abandoned:

task.Wait(2000);

The other overloads work in a similar manner.

View the list of posts on the Task Parallel Library here.

Suspending a Task using SpinWait in .NET C#

You may want to put a Task to sleep for some time. You might want to check the state of an object before continuing. The Task continues after the sleep time.

One way to solve this is using the Thread.SpinWait method since the Task library uses .NET threading support in the background.

First construct your cancellation token:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

Use this token in the constructor of the task:

Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		Thread.SpinWait(10000);
		Console.WriteLine(i);
		cancellationToken.ThrowIfCancellationRequested();
	}
}, cancellationToken);

You can use the cancellation token to interrupt the task:

cancellationTokenSource.Cancel();

We looked at Thread.Sleep and the WaitOne method in the previous posts on TPL. With those two techniques the thread that’s currently performing the task gives up its turn while sleeping. This gives a chance to other threads to do something. With spin waiting it’s different as the thread does not give up its turn.

The integer argument in SpinWait indicates an iteration. It is the number of times that a tight CPU loop will be performed. The exact amount of sleep time will depend on the speed of the CPU so it’s not some well defined period of time. Therefore spin waiting doesn’t stop using the CPU.

Bear in mind these limitations and use SpinWait with care – if ever.

View the list of posts on the Task Parallel Library here.

Cancel multiple Tasks at once in .NET C#

You cannot directly interrupt a Task in .NET while it’s running. You can do it indirectly through the CancellationTokenSource object. This object has a CancellationToken property which must be passed into the constructor of the Task:

CancellationTokenSource cancellationTokenSource	= new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

You can re-use the same token in the constructor or several tasks:

Task firstTask = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
	}
}, cancellationToken);

Task secondTask = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < int.MaxValue; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
	}
}, cancellationToken);

We simply count up to 100000 in the Task bodies. We’re using the ThrowIfCancellationRequested() method of the cancellation token, I’ll explain shortly what it does.

You can cancel both tasks by calling the Cancel() method of the token like this:

cancellationTokenSource.Cancel();

Note that this method only signals the wish to cancel a task. .NET will not actively interrupt the task, you’ll have to monitor the status through the IsCancellationRequested property. It is your responsibility to stop the task. In this example we’ve used the built-in “convenience” method ThrowIfCancellationRequested() which throws an OperationCanceledException which is a must in order to correctly acknowledge the cancellation. If you forget this step then the task status will not be set correctly. Once the task has been requested the stop it cannot be restarted.

If you need to do something more substantial after cancellation, such as a resource cleanup, then you can monitor cancellation through the IsCancellationRequested and throw the OperationCanceledException yourself:

for (int i = 0; i < 100000; i++)
{
	if (token.IsCancellationRequested)
	{
		Console.WriteLine("Task cancellation requested");
		throw new OperationCanceledException(token);
	}
	else
	{
		Console.WriteLine(i);
	}
}

View the list of posts on the Task Parallel Library here.

Monitoring Task cancellation in .NET C# using a Wait handle

You cannot directly interrupt a Task in .NET while it’s running. You can do it indirectly through the CancellationTokenSource object. This object has a CancellationToken property which must be passed into the constructor of the Task:

CancellationTokenSource cancellationTokenSource	= new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

The cancellation token can be used as follows:

// create the task
Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		if (cancellationToken.IsCancellationRequested)
		{
			Console.WriteLine("Task cancelled, must throw exception.");
			throw new OperationCanceledException(cancellationToken);
		}
		else
		{
			Console.WriteLine(i);
		}
	}
}, cancellationToken);

We simply count up to 100000 in the Task body. Note the IsCancellationRequested property of the token. We monitor within the loop whether the task has been cancelled.

The cancellation token has a property called WaitHandle which has a method called WaitOne(). This method blocks until the Cancel() method is called on the token source provided in the Task constructor. It can be used by setting up another Task which calls the WaitOne method which blocks until the first Task has been cancelled:

Task secondTask = Task.Factory.StartNew(() =>
	{
		cancellationToken.WaitHandle.WaitOne();
		Console.WriteLine("WaitOne called.");
	});

You can cancel the task by calling the Cancel() method of the token like this:

cancellationTokenSource.Cancel();

Note that this method only signals the wish to cancel a task. .NET will not actively interrupt the task, you’ll have to monitor the status through the IsCancellationRequested property. It is your responsibility to stop the task. In this example we throw an OperationCanceledException which is a must in order to correctly acknowledge the cancellation. If you forget this step then the task status will not be set correctly. Once the task has been requested the stop it cannot be restarted.

If that’s all you want to do, i.e. throw an OperationCanceledException, then there’s a shorter version:

cancellationToken.ThrowIfCancellationRequested();

This will perform the cancellation check and throw the exception in one step. The loop can thus be simplified as follows:

Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		//shorthand
        	cancellationToken.ThrowIfCancellationRequested();

		Console.WriteLine(i);
	}
}, cancellationToken);

View the list of posts on the Task Parallel Library here.

Exception handling in the .NET Task Parallel Library with C#: a safety catch-all

In the previous posts on exception handling in Tasks (here, here, and here) we saw how to handle exceptions thrown by tasks. We saw that unhandled aggregate exceptions will be re-thrown by the default escalation policy. This will lead your application to be terminated immediately. If there are other ongoing tasks in that moment then those will be terminated too.

There can be situations that your application uses threads to such an extent that you either cannot put a try-catch block around every single Task.Wait, Task.WaitAll etc. calls or you simply forget it. There is a way to subscribe to all unhandled aggregate exceptions that the task scheduler encounters. You can then decide what to do with the exception there.

The task scheduler has an UnobservedTaskException event. It’s straightforward to subscribe to that even with the += operator.

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

…where the event handler looks like this:

static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
	e.SetObserved();
	((AggregateException)e.Exception).Handle(ex =>
	{
		Console.WriteLine("Exception type: {0}", ex.GetType());
		return true;
	});
}

The SetObserved() method tells the task scheduler that the exception has been taken care of so there’s no need for it to bubble up. The UnobservedTaskExceptionEventArgs object has an Exception property that must be cast to an AggregateException. From then on you can call the Handle() method of the aggregate exception as we saw before.

Start a couple of new tasks:

Task.Factory.StartNew(() =>
{
	throw new ArgumentException();
});

Task.Factory.StartNew(() =>
{
	throw new NullReferenceException();
});

Wait a bit for the tasks to finish:

Thread.Sleep(100);

Run the code with Ctrl+F5 and… er… nothing happens really. The event handler is never triggered. You won’t see the aggregate exception type printed on the console window. What happened? The UnobservedTaskException handler will be called when the tasks with the unhandled exception have been collected by the garbage collector. As long as we are holding a reference to the two tasks the GC will not collect them, and we’ll never see the exception handler in action.

If we want to force the event handler to be fired then you can add the following two lines just below the Thread.Sleep bit:

GC.Collect();
GC.WaitForPendingFinalizers();

Run the code again and you’ll see the exception messages in the console window.

Note the following: in .NET4.5 there’s a new configuration element that specifies whether unhandled task exceptions should terminate the application or not:

<ThrowUnobservedTaskExceptions
   enabled="true|false"/>

True: terminate the process if an unhandled exception is encountered.
False (default): the exact opposite of the True case

In .NET4 the default behaviour is to terminate the process as we saw. In .NET4.5 it’s the exact opposite: unhandled exceptions still cause the UnobservedTaskException event to be raised, but the process will not be terminated by default. The exception will be silently ignored. So if you want to simulate .NET4 behaviour in a multi-threaded .NET4.5 application you’ll need to set the above mentioned configuration setting to true in the config file.

View the list of posts on the Task Parallel Library here.

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

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