Final Thoughts on CancellationTokenSource with Tasks and Parallel Operations

So, after running all of these tests and observing the different behaviors, I think I can provide a summary on what to expect when cancelling tasks and parallel operations in this way. I also think it is important to understand this since cancellation behavior should be as consistent and predictable as possible. So here are my observed conclusions:

General Guidelines
In every case, canceling a Task that has already completed has no effect on anything and attempting to cancel a Task that has not yet started will throw an InvalidOperationException.

Tasks
When you run your own Task over an Action and you have passed a CancellationToken to both the Task and the Action, you have these scenarios:

  1. If the Action has started but is not yet in a status of “Running”, canceling the CancelationTokenSource will automatically fire the TaskCanceledException and terminate the Task, leaving it in a final status of “Canceled”.
  2. However, if the Action has started and is in a status of “Running”, the Action could respond to the IsCancellationRequested property of the CancellationToken and complete its work gracefully. In this case, the Task will also complete and report a final status of “RanToCompletion”. The cancel is “swallowed” by the Action, in this case and the Task is none the wiser.
  3. Alternatively, but still assuming that the Action has started and is in a status of “Running”, the Action could call the token.ThrowIfCancellationRequested() method which will terminate the Action immediately as well as terminate the Task. This leaves the Task with a final status of “Canceled”.

Parallel Operations
When using Parallel.For, Parallel.ForEach, and Parallel LINQ, the behavior is a little different. In these cases, the Cancel() operation is used for preventing the Parallel object from starting new operations, but leaves the operations that have already started alone. The operations, themselves, are unaware of the cancellation and have no reference to it.

Advanced
If you wanted to set up an advanced situation, such as passing CancellationToken objects to Actions within a Parallel operation, be aware that the Framework communicates cancellation through Exceptions. The TaskCanceledException is fired for tasks that follow the patterns described in previous posts and the OperationCanceledException is fired for Parallel Operations. If you were passing CancellationToken objects to Actions within a Parallel operation, it might be wise to allow those Actions to handle the cancellation gracefully (i.e. don’t fire token.ThrowIfCancellationRequested within them) so that the general pattern of the Parallel operation is unaffected. It will automatically stop new operations from running, but will allow your already-running operations to run to a state of completion (which may include your graceful exit based on the token). I don’t know how often this type of situation would be necessary, but I’m sure someone is using it or trying it somewhere.

Also, there are additional constructs for controlling Parallel loops using a ParallelLoopState for breaking and stopping loops in specific ways, the discussion of these is outside my scope of CancellationTokenSource. See this msdn site as a starting point: Parallel.For Documentation

Posted in Asynchrony, Parallel | Tagged , | 1 Comment

Understanding CancellationTokenSource with Parallel

CancellationTokenSource also works with Parallel.For, Parallel.ForEach, and Parallel LINQ much like with Task. The behavior is similar, but not the same. When starting a Parallel.For or ForEach, you can pass in a ParallelOptions object that contains the CancellationToken. When starting Parallel LINQ, you can call AsParallel().WithCancellation(token). Once the Parallel operation is running, you can cancel the CancellationTokenSource to cancel it. In this case, the Parallel class is handling that cancel, not any code that you provided, so there is more consistency overall. Unless the Parallel operation has already run to its completion, the Cancel will stop it and it will throw an OperationCanceledException (not a TaskCanceledException). Here is some additional info from “Essential C# 4.0”:

Note that internally the parallel loop case prevents new iterations that haven’t started yet from commencing via the IsCancellationRequested property. Existing executing iterations will run to their respective termiation points. Furthermore, calling Cancel() even after all iterations have completed will still cause the registered cancel event (via cts.Token.Register()) to execute.

Here are some unit tests for Parallel.For, Parallel.ForEach, and Parallel LINQ to show this. Note that I placed the Parallel operations within a Task, but this is only so that I could cancel the operation while the Task was running:

[TestMethod]
public void ParallelForCancelsWithToken()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	//This is how to pass a token to Parallel.For or Parallel.ForEach
	var options = new ParallelOptions() { CancellationToken = token };
	bool parallelThrew = false;
	var outerTask = Task.Factory.StartNew(() =>
		{
			try
			{
				Parallel.For(0, 5, options, (i) => Thread.Sleep(500));
			}
			catch (Exception ex)
			{
				Assert.IsInstanceOfType(ex, typeof(OperationCanceledException));
				parallelThrew = true;
			}
		}
	);
	tokenSource.Cancel();
	outerTask.Wait();
	Assert.IsTrue(parallelThrew);
}
[TestMethod]
public void ParallelForEachCancelsWithToken()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	//This is how to pass a token to Parallel.For or Parallel.ForEach
	var options = new ParallelOptions() { CancellationToken = token };
	bool parallelThrew = false;
	var itemsToIterate = new List<string> { "one", "two", "three" };
	var outerTask = Task.Factory.StartNew(() =>
	{
		try
		{
			Parallel.ForEach(itemsToIterate, options, (i) => Thread.Sleep(500));
		}
		catch (Exception ex)
		{
			Assert.IsInstanceOfType(ex, typeof(OperationCanceledException));
			parallelThrew = true;
		}
	}
	);
	tokenSource.Cancel();
	outerTask.Wait();
	Assert.IsTrue(parallelThrew);
}
[TestMethod]
public void PlinqCancelsWithToken()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	bool plinqThrew = false;
	var itemsToIterate = new List<string> { "one", "two", "three" };

	var outerTask = Task.Factory.StartNew(() =>
	{
		try
		{
			var result = itemsToIterate.AsParallel().WithCancellation(token)
				.Select((item) =>
					{
						Thread.Sleep(500);
						return item.ToString();
					}
			);
			//Must force execution or exception will not throw
			Assert.IsTrue(result.Count() < 3);
		}
		catch (Exception ex)
		{
			Assert.IsInstanceOfType(ex, typeof(OperationCanceledException));
			plinqThrew = true;
		}
	}
	);
	tokenSource.Cancel();
	outerTask.Wait();
	Assert.IsTrue(plinqThrew);
}
Posted in Asynchrony, Parallel, Unit Tests | Tagged , , | 2 Comments

Understanding CancellationTokenSource with Tasks: Part 2

After some more testing and digging, I have discovered a way to keep Task cancellation behavior more consistent. In my previous post, I was allowing the Action within each Task to exit gracefully when it evaluated the IsCancellationRequested property of the CancellationToken. It turns out that this is why the Task wrapping that Action did not report a “true” result for IsCanceled once the Action exited. In order for the Task to know that its Action was canceled (at least once that Action is running as we established before), the Action has to throw the TaskCanceledException. It does this by calling the ThrowIfCancellationRequested method on the CancellationToken object that it was given. See this unit test for an example:

[TestMethod]
public void RunningTaskDoesRespondToThrowInsideAction()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	Task taskWithToken = new Task(
		() =>
		{
			while (true)
			{
				token.ThrowIfCancellationRequested();
			}
		}, token
	);
	taskWithToken.Start();
	while (taskWithToken.Status != TaskStatus.Running)
	{
		//Wait until task is running
	}
	if (taskWithToken.Status != TaskStatus.Faulted)
	{
		tokenSource.Cancel();
		bool taskThrewExceptionUponWait = false;
		try
		{
			taskWithToken.Wait();
		}
		catch (Exception ex)
		{
			Assert.IsInstanceOfType(ex, typeof(AggregateException));
			var inner = ((AggregateException)ex).InnerExceptions[0];
			Assert.IsInstanceOfType(inner, typeof(TaskCanceledException));
			taskThrewExceptionUponWait = true;
		}
		Assert.IsTrue(taskWithToken.Status == TaskStatus.Canceled);
		Assert.IsTrue(taskThrewExceptionUponWait);
	}
	else
	{
		Assert.Inconclusive("Task faulted");
		try
		{
			tokenSource.Cancel(); // Clean up
		}
		catch { }
	}
}

You might think to yourself now, as I did, “well, if throwing a specific exception type is what notifies the Task of cancellation, does the Task really need a reference to the token in this case?”. It turns out that yes, it does. Without the reference to the Token in the Task constructor, the Action’s “throw” results in the Task being faulted instead of canceled. See this example:

[TestMethod]
public void TaskWithoutTokenReportsNotCanceled_EvenWhenActionThrows()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	Task taskWithToken = new Task(
		() =>
		{
			while (true)
			{
				token.ThrowIfCancellationRequested();
			}
		} //No token
	);
	taskWithToken.Start();
	while (taskWithToken.Status != TaskStatus.Running)
	{
		//Wait until task is running
	}
	if (taskWithToken.Status != TaskStatus.Faulted)
	{
		tokenSource.Cancel();
		bool taskThrewExceptionUponWait = false;
		try
		{
			taskWithToken.Wait();
		}
		catch (Exception ex)
		{
			Assert.IsInstanceOfType(ex, typeof(AggregateException));
			var inner = ((AggregateException)ex).InnerExceptions[0];
			Assert.IsInstanceOfType(inner, typeof(OperationCanceledException));
			taskThrewExceptionUponWait = true;
		}
		Assert.IsFalse(taskWithToken.Status == TaskStatus.Canceled);
		Assert.IsTrue(taskWithToken.Status == TaskStatus.Faulted);
		Assert.IsTrue(taskThrewExceptionUponWait);
	}
	else
	{
		Assert.Inconclusive("Task faulted");
		try
		{
			tokenSource.Cancel(); // Clean up
		}
		catch { }
	}
}

So, Aha! We figured out some more about this. I will follow up with another post detailing results from Parallel.For, Parallel.ForEach and PLINQ with CancellationTokens as well.

Posted in Asynchrony, Unit Tests | Tagged , | 5 Comments

Understanding CancellationTokenSource with Tasks

I’ve been ramping up my knowledge of the new features coming in .NET 4.5 and C# 5 lately, namely the new features around asynchronous programming.  The Task class is at the heart of these new features, so I have been examining the Task class more closely so that I can use it properly.  I was reading through “Essential C# 4.0” and “C# 4.0 In a Nutshell” on the Task class and came across some examples of task cancellation using tokens for “cooperative cancellation”.  What caught my eye first was that, when using this pattern with a Task object, the Task constructor AND the inner Action BOTH need a reference to the CancellationToken. For example:

Task task = Task.TaskFactory.StartNew(() => WritePi(cancellationTokenSource.Token), cancellationTokenSource.Token);

This did not make sense to me at first, so I wrote some unit tests around it to see what would happen when the Task was canceled in different scenarios.  Here is what I discovered:

Discovery # 1 – A running task does not respond to the Task’s CancellationToken, but the Action within it does respond to its own token

In other words, once a Task has a Status of “Running”, calling Cancel() on the CancellationTokenSource no longer has an effect on the actual Task, it is up to the Action within the Task to respond to the token’s message.  Additionally, even though the Task has been “canceled”, it reports a status of “RanToCompletion” as if nothing unusual happened!  Here are some of my unit tests which demonstrate this:

[TestMethod]
public void RunningTaskDoesNotRespondToCancel_ButActionDoes()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	Task taskWithToken = new Task(
		() =>
		{
			while (true)
			{
				if (tokenSource.IsCancellationRequested)
				{
					break;
				}
			}
		}, token
	);
	taskWithToken.Start();
	while (taskWithToken.Status != TaskStatus.Running)
	{
		//Wait until task is running
	}
	tokenSource.Cancel();  //cancel...
	taskWithToken.Wait();  //...and wait for the action within the task to complete
	Assert.IsFalse(taskWithToken.Status == TaskStatus.Canceled);
	Assert.IsTrue(taskWithToken.Status == TaskStatus.RanToCompletion);
}
[TestMethod]
public void RunningTaskDoesNotContinueToCancel()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	Task taskWithToken = new Task(
		() =>
		{
			while (true)
			{
				if (tokenSource.IsCancellationRequested)
				{
					break;
				}
			}
		}, token
	);
	var canceledTask = taskWithToken.ContinueWith(
		(antecedentTask) => Assert.Fail("Canceled"), 
		TaskContinuationOptions.OnlyOnCanceled);
	var completedTask = taskWithToken.ContinueWith(
		(antecedentTask) => Assert.IsTrue(true), 
		TaskContinuationOptions.OnlyOnRanToCompletion);
	taskWithToken.Start();
	while (taskWithToken.Status != TaskStatus.Running)
	{
		//Wait until task is running
	}
	tokenSource.Cancel();
	taskWithToken.Wait();
	completedTask.Wait();
	// The completedTask continuation should have run to completion
	// since its antecendent task (taskWithToken) also ran to completion.
	Assert.IsTrue(completedTask.Status == TaskStatus.RanToCompletion);
	// The canceledTask continuation should have been canceled since
	// its antecedent task (taskWithToken) ran to completion.
	Assert.IsTrue(canceledTask.Status == TaskStatus.Canceled);
}

Discovery #2 – Tasks can be canceled after they “start” but before they are “running”

So, after my first discovery, I was thinking to myself “what is the purpose of handing a token to a task if the actual task object doesn’t even recognize the cancel operation?”  It turns out that the task DOES recognize the cancel operation, but only when the task is in certain status conditions.  Basically, if the task was started but isn’t actually yet running, it can be effectively canceled through the token.  This unit test works, but depending on your environment, it may result in an inconclusive result due to the fact that the task may have started before the test could cancel it. Run it a few times in a row if you get inconclusive and you will likely get a few “Pass” results as well:

[TestMethod]
public void NonRunningTaskDoesRespondToCancel_AndActionDoesToo()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	Task taskWithToken = new Task(
		() =>
		{
			while (true)
			{
				if (tokenSource.IsCancellationRequested)
				{
					break;
				}
			}
		}, token
	);
	taskWithToken.Start();
	if (taskWithToken.Status != TaskStatus.Running)
	{
		tokenSource.Cancel();  //cancel...
		bool taskThrewExceptionUponWait = false;
		try
		{
			taskWithToken.Wait();  //...and wait for the action within the task to complete
		}
		catch (Exception ex)
		{
			Assert.IsInstanceOfType(ex, typeof(AggregateException));
			var inner = ((AggregateException)ex).InnerExceptions[0];
			Assert.IsInstanceOfType(inner, typeof(TaskCanceledException));
			taskThrewExceptionUponWait = true;
		}
		Assert.IsTrue(taskWithToken.Status == TaskStatus.Canceled);
		Assert.IsTrue(taskThrewExceptionUponWait);
	}
	else
	{
		Assert.Inconclusive("Task was already running when cancel would have fired");
		try
		{
			tokenSource.Cancel(); // Clean up
		}
		catch { }
	}
}
[TestMethod]
public void NonRunningTaskDoesContinueToCancel()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	Task taskWithToken = new Task(
		() =>
		{
			while (true)
			{
				if (tokenSource.IsCancellationRequested)
				{
					break;
				}
			}
		}, token
	);
	var canceledTask = taskWithToken.ContinueWith(
		(antecedentTask) => Assert.IsTrue(true), 
		TaskContinuationOptions.OnlyOnCanceled);
	var completedTask = taskWithToken.ContinueWith(
		(antecedentTask) => Assert.Fail("Completed"), 
		TaskContinuationOptions.OnlyOnRanToCompletion);
	taskWithToken.Start();
	if (taskWithToken.Status != TaskStatus.Running)
	{
		tokenSource.Cancel();
		try
		{
			taskWithToken.Wait();
		}
		catch
		{
			// Not interested in the exceptions for this test
		}
		canceledTask.Wait();
		// The completedTask continuation should have been canceled
		// since its antecedent task (taskWithToken) was canceled
		// and did not run to completion.
		Assert.IsTrue(completedTask.Status == TaskStatus.Canceled);
		// The canceledTask continuation should have run to completion
		// since its antecendent task (taskWithToken) was canceled.
		Assert.IsTrue(canceledTask.Status == TaskStatus.RanToCompletion);
	}
	else
	{
		Assert.Inconclusive("Task was already running when cancel would have fired");
		try
		{
			tokenSource.Cancel(); // Clean up
		}
		catch { }
	}
}

Discovery #3 – Tasks that have not been started can not be properly canceled.

This just illustrates that the cancel operation has to occur between Start() and the Running status; it can’t precede Start() entirely.

[TestMethod]
public void CanceledTaskCanNotBeStarted()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	Task taskWithToken = new Task(
		() =>
		{
			while (true)
			{
				if (tokenSource.IsCancellationRequested)
				{
					break;
				}
			}
		}, token
	);
	tokenSource.Cancel();
	bool runtimeThrewException = false;
	try
	{
		taskWithToken.Start();
	}
	catch (Exception ex)
	{
		Assert.IsInstanceOfType(ex, typeof(InvalidOperationException));
		runtimeThrewException = true;
	}
	Assert.IsTrue(runtimeThrewException);
}

Discovery #4 – Tasks without a CancellationToken do not respond to a cancellation even if their internal Action does

This one is not surprising, but is here for the sake of completion. It is, however, still good to know that the Action within the Task will respond to the CancellationToken even if the Task is unaware of it.

[TestMethod]
public void TaskWithoutTokenReportsNotCanceled()
{
	CancellationTokenSource tokenSource = new CancellationTokenSource();
	Task taskWithToken = new Task(
		() =>
		{
			while (true)
			{
				if (tokenSource.IsCancellationRequested)
				{
					break;
				}
			}
		} // No Token
	);
	taskWithToken.Start();
	tokenSource.Cancel();
	taskWithToken.Wait();
	Assert.IsFalse(taskWithToken.IsCanceled);
	Assert.IsTrue(taskWithToken.Status == TaskStatus.RanToCompletion);
}

Conclusion

Basically, the CancellationToken objects involved serve different purposes within the pattern shown earlier.  The token passed to the Action within the Task is used to stop the Action gracefully, or cooperatively, within the logical rules of that particular action and therefore hopefully leaving the state of things in a proper place whether canceled or not.  The token passed to the Task itself, however, is used to stop the task from running if it has not yet entered the “Running” status.

It is also good to know that if the Task’s token object is canceled before the task status is “Running”, the task will throw an AggregateException with a TaskCanceledException InnerException once Task.Wait() is called.  If the task was already running when the CancellationTokenSource is canceled, however, no exceptions are thrown and the Action runs per its own rules and the Task runs to completion as far as it is concerened.

So, when using this pattern with the Task class, be aware that canceling a Task may not cause IsCanceled to be true, and likewise may not cause TaskCreationOptions.OnlyOnCanceled continuations to fire.  To me, this isn’t very intuitive but I suppose it is the difference between canceling and aborting.

UPDATE: See the next post for more information on this issue!

Posted in Asynchrony, Unit Tests | Tagged , | 10 Comments