A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects.
The wait handle of the cancellation token will become signaled in response to a cancellation request, and the method can use the return value of the WaitAny method to determine whether it was the cancellation token that signaled. The operation can then just exit, or throw a OperationCanceledException, as appropriate.
You should always dispose CancellationTokenSource .
A CancellationTokenSource object, which provides a cancellation token through its Token property and sends a cancellation message by calling its Cancel or CancelAfter method. A CancellationToken object, which indicates whether cancellation is requested.
Passing a CancellationToken
into the Task
constructor associates it with the task.
Quoting Stephen Toub's answer from MSDN:
This has two primary benefits:
- If the token has cancellation requested prior to the
Task
starting to execute, theTask
won't execute. Rather than transitioning toRunning
, it'll immediately transition toCanceled
. This avoids the costs of running the task if it would just be canceled while running anyway.- If the body of the task is also monitoring the cancellation token and throws an
OperationCanceledException
containing that token (which is whatThrowIfCancellationRequested
does), then when the task sees thatOperationCanceledException
, it checks whether theOperationCanceledException
's token matches the Task's token. If it does, that exception is viewed as an acknowledgement of cooperative cancellation and theTask
transitions to theCanceled
state (rather than theFaulted
state).
The constructor uses the token for cancellation handling internally. If your code would like access to the token you are responsible for passing it to yourself. I would highly recommend reading the Parallel Programming with Microsoft .NET book at CodePlex.
Example usage of CTS from the book:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task myTask = Task.Factory.StartNew(() =>
{
for (...)
{
token.ThrowIfCancellationRequested();
// Body of for loop.
}
}, token);
// ... elsewhere ...
cts.Cancel();
Cancellation is not a simple a case as many might think. Some of the subtleties are explained in this blog post on msdn:
For example:
In certain situations in Parallel Extensions and in other systems, it is necessary to wake up a blocked method for reasons that aren't due to explicit cancellation by a user. For example, if one thread is blocked on
blockingCollection.Take()
due to the collection being empty and another thread subsequently callsblockingCollection.CompleteAdding()
, then the first call should wake up and throw anInvalidOperationException
to represent an incorrect usage.
Cancellation in Parallel Extensions
Here is a code example that demonstrates the two points in the accepted answer by Max Galkin:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("*********************************************************************");
Console.WriteLine("* Start canceled task, don't pass token to constructor");
Console.WriteLine("*********************************************************************");
StartCanceledTaskTest(false);
Console.WriteLine();
Console.WriteLine("*********************************************************************");
Console.WriteLine("* Start canceled task, pass token to constructor");
Console.WriteLine("*********************************************************************");
StartCanceledTaskTest(true);
Console.WriteLine();
Console.WriteLine("*********************************************************************");
Console.WriteLine("* Throw if cancellation requested, don't pass token to constructor");
Console.WriteLine("*********************************************************************");
ThrowIfCancellationRequestedTest(false);
Console.WriteLine();
Console.WriteLine("*********************************************************************");
Console.WriteLine("* Throw if cancellation requested, pass token to constructor");
Console.WriteLine("*********************************************************************");
ThrowIfCancellationRequestedTest(true);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Test Completed!!!");
Console.ReadKey();
}
static void StartCanceledTaskTest(bool passTokenToConstructor)
{
Console.WriteLine("Creating task");
CancellationTokenSource tokenSource = new CancellationTokenSource();
Task task = null;
if (passTokenToConstructor)
{
task = new Task(() => TaskWork(tokenSource.Token, false), tokenSource.Token);
}
else
{
task = new Task(() => TaskWork(tokenSource.Token, false));
}
Console.WriteLine("Canceling task");
tokenSource.Cancel();
try
{
Console.WriteLine("Starting task");
task.Start();
task.Wait();
}
catch (Exception ex)
{
Console.WriteLine("Exception: {0}", ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
}
}
Console.WriteLine("Task.Status: {0}", task.Status);
}
static void ThrowIfCancellationRequestedTest(bool passTokenToConstructor)
{
Console.WriteLine("Creating task");
CancellationTokenSource tokenSource = new CancellationTokenSource();
Task task = null;
if (passTokenToConstructor)
{
task = new Task(() => TaskWork(tokenSource.Token, true), tokenSource.Token);
}
else
{
task = new Task(() => TaskWork(tokenSource.Token, true));
}
try
{
Console.WriteLine("Starting task");
task.Start();
Thread.Sleep(100);
Console.WriteLine("Canceling task");
tokenSource.Cancel();
task.Wait();
}
catch (Exception ex)
{
Console.WriteLine("Exception: {0}", ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
}
}
Console.WriteLine("Task.Status: {0}", task.Status);
}
static void TaskWork(CancellationToken token, bool throwException)
{
int loopCount = 0;
while (true)
{
loopCount++;
Console.WriteLine("Task: loop count {0}", loopCount);
token.WaitHandle.WaitOne(50);
if (token.IsCancellationRequested)
{
Console.WriteLine("Task: cancellation requested");
if (throwException)
{
token.ThrowIfCancellationRequested();
}
break;
}
}
}
}
Output:
*********************************************************************
* Start canceled task, don't pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Task: loop count 1
Task: cancellation requested
Task.Status: RanToCompletion
*********************************************************************
* Start canceled task, pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Exception: Start may not be called on a task that has completed.
Task.Status: Canceled
*********************************************************************
* Throw if cancellation requested, don't pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: The operation was canceled.
Task.Status: Faulted
*********************************************************************
* Throw if cancellation requested, pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: A task was canceled.
Task.Status: Canceled
Test Completed!!!
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