230

Certain System.Threading.Tasks.Task constructors take a CancellationToken as a parameter:

CancellationTokenSource source = new CancellationTokenSource();
Task t = new Task (/* method */, source.Token);

What baffles me about this is that there is no way from inside the method body to actually get at the token passed in (e.g., nothing like Task.CurrentTask.CancellationToken). The token has to be provided through some other mechanism, such as the state object or captured in a lambda.

So what purpose does providing the cancellation token in the constructor serve?

i3arnon
  • 101,022
  • 27
  • 291
  • 322
Colin
  • 2,693
  • 3
  • 17
  • 14

4 Answers4

261

Passing a CancellationToken into the Task constructor associates it with the task.

Quoting Stephen Toub's answer from MSDN:

This has two primary benefits:

  1. If the token has cancellation requested prior to the Task starting to execute, the Task won't execute. Rather than transitioning to Running, it'll immediately transition to Canceled. This avoids the costs of running the task if it would just be canceled while running anyway.
  2. If the body of the task is also monitoring the cancellation token and throws an OperationCanceledException containing that token (which is what ThrowIfCancellationRequested does), then when the task sees that OperationCanceledException, it checks whether the OperationCanceledException's token matches the Task's token. If it does, that exception is viewed as an acknowledgement of cooperative cancellation and the Task transitions to the Canceled state (rather than the Faulted state).
Eliahu Aaron
  • 3,631
  • 2
  • 23
  • 32
Max Galkin
  • 16,264
  • 9
  • 63
  • 108
27

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();
user7116
  • 60,025
  • 16
  • 134
  • 166
  • 3
    and what happens if you do not pass token as parameter? Looks like behaviour will be the same, no purpose. – sergtk Nov 09 '11 at 21:54
  • 2
    @sergdev: you pass the token to register it with the task and scheduler. Not passing it and using it would be undefined behavior. – user7116 Nov 09 '11 at 22:10
  • 3
    @sergdev: after testing: myTask.IsCanceled and myTask.Status are not same when you do not pass the token as parameter. The status will be failed instead of canceled. Nonetheless the exception is the same: it's a OperationCanceledException in both case. – Olivier de Rivoyre Jul 16 '15 at 10:06
  • 2
    What if I don't call `token.ThrowIfCancellationRequested();`? In my test, the behavior is the same. Any ideas? – machinarium Jul 26 '15 at 14:34
  • @machinarium @sergtk: If you pass the token from a `CancellationTokenSource` into a Task constructor, when `cts.Cancel()` is called the Task is going to get canceled and end, no matter what you do (or don't do) in your method body. By default, the canceled Task's status is set to _Faulted_ to indicate it ended **without your consent**. If you monitor for intentional cancellation via `token.ThrowIfCancellationRequested()` the Task's status will be set to _Canceled_ when `tcs.Cancel()` is called, because your code acknowledged it knew about the cancellation request and took appropriate action. – CobaltBlue Aug 29 '16 at 13:44
  • 3
    @CobaltBlue: `when cts.Cancel() is called the Task is going to get canceled and end, no matter what you do` nope. If the Task is canceled before it has started, it is _Canceled_. If the body of the Task simply never checks any token, it will run to completion, resulting in a _RanToCompletion_ status. If the body throws an `OperationCancelledException`, e.g. by `ThrowIfCancellationRequested`, then Task will check whether that Exception's CancellationToken is the same as the one associated with the Task. If it is, the task is _Canceled_. If not, it's _Faulted_. – Wolfzoon Oct 03 '16 at 16:49
7

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 calls blockingCollection.CompleteAdding(), then the first call should wake up and throw an InvalidOperationException to represent an incorrect usage.

Cancellation in Parallel Extensions

Eliahu Aaron
  • 3,631
  • 2
  • 23
  • 32
x0n
  • 47,695
  • 5
  • 84
  • 110
6

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!!!
Eliahu Aaron
  • 3,631
  • 2
  • 23
  • 32