Parallel Computing Developer Center > Parallel Computing Forums > Parallel Extensions to the .NET Framework > Cancellation Framework in Beta 2 (CancellationTokenSource and Token)
Ask a questionAsk a question
 

AnswerCancellation Framework in Beta 2 (CancellationTokenSource and Token)

  • Friday, October 23, 2009 8:42 AMMarcZhou Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    Hello,

    I've a simple question about the new Cancellation Framework in .NET 4.0 Beta 2. I saw the example in the PFX Team Blog about using the new approach and I've some doubts about the using of the Token. Here is my example:

    public static void Example1()
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        Task myTask = Task.Factory.StartNew(() =>
        {
            for (; ; )
            {
                Console.WriteLine("Check for cancellation ...");
                <strong>cts.Token</strong>.ThrowIfCancellationRequested();
                Console.WriteLine("wait");
                Thread.CurrentThread.Join(500);
            }
    
        }, <strong>cts.Token</strong>);
        Thread.CurrentThread.Join(2000);
        cts.Cancel();
    }
    
    I need to understand the second parameter: Task.Factory.StartNew(..., CancellationToken). It is possible to use this value inside the created Task in the user code? I noticed that the value is assigned inside the Task class to an internal structure. Since the property Task.Current is removed, I've no reference to the Task instance itself. In the examle above I'm able to access the Token, because the CancellationTokenSource instance is declared as global variable. When I change the example to the version below, I've the same result (but I think it is not correct ...):

    public static void Example1()
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        Task myTask = Task.Factory.StartNew(() =>
        {
            Task.Factory.StartNew(() =>
            {
                for (; ; )
                {
                    Console.WriteLine("Check for cancellation ...");
                    cts.Token.ThrowIfCancellationRequested();
                    Console.WriteLine("wait INNER");
                    Thread.CurrentThread.Join(500);
                }
            }, cts.Token);
    
            for (; ; )
            {
                Console.WriteLine("Check for cancellation ...");
                token.ThrowIfCancellationRequested();
                Console.WriteLine("wait");
                Thread.CurrentThread.Join(500);
            }
    
        });
        Thread.CurrentThread.Join(2000);
        cts.Cancel();
    }
    
    In the example above I've removed the second parameter during the call to StartNew. I'm able to use the cts.Token inside the Task, because the the variable is available in this scope.
    What happens inside the Task with the Token? It is necessary to pass everytime the token to the Task object?
    Do you have a best practice or a pattern to use the Cancellation Framework correct?
    -

Answers

  • Saturday, October 24, 2009 1:58 AMDanny ShihMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Hi Marc and Andy,

    Thanks for the feedback!

    Re: Why isn't the CancellationToken accessible via the Task object?
    A TaskCompletionSource<TResult>.Task can enter the Canceled state via the SetCanceled method.  Continuation Tasks enter the Canceled state if they are created with one of the NotOn* or OnlyOn* options, and they don't need to run.  In both cases, a Task can become Canceled without its CancellationToken being signaled.  We removed the Task.CancellationToken property to avoid this inconsistency.  We also figured that the token would be in scope (and accessible to the Task delegate) for most scenarios, and on the occasion that it's not, the state object can be used, as you suggested.

    Re: Is it necessary to pass the CancellationToken to the Task object every time?
    It depends on your scenario.  When a Task throws an OperationCanceledException, the following must be true for the Task to enter the Canceled state:
    (1) The OCE contains the CancellationToken that is associated with the Task - in other words, the token that was passed to the API that created the Task.
    (2) That CancellationToken is signaled; its IsCancellationRequested property is true.

    If any of those conditions is false, the Task will transition to the Faulted state.  So as per (1), you do need to pass the token if you want the Task to terminate in the Canceled state.  However, that's not always necessary.  In a lot of scenarios, you can check for cancellation requests and simply return out of the Task delegate.  Note that the Task will terminate in the RanToCompletion state, though.  This can become an issue if you have logic that depends on Task states, e.g. using continuation Tasks that were created with a NotOn* or OnlyOn* option.

    Hope this helps,
    Danny

All Replies

  • Friday, October 23, 2009 11:04 AMAndy Clymer Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Im with you and it has concerned me too, I can totally see having access to the underlying Task wasn't a good thing, but since you have the opportunity to pass the CanellationToken to the Task API its a shame it couldn't flow the token into the task.  After all we have access to Task.CurrentId it would be nice to have Task.CurrentCancelationToken, or use a different delegate for the task body to contain the cancellation token, or I guess you could flow the cancellation token as the object parameter to Start, but that does seem to odd to supply it twice.


    Here is a simple class I put together that I think solves the issue, however it does assume that a task always runs on the same thread once it starts.  Which I think is a fair assumption at the moment.

        public class CancellableTask : Task
        {
            [ThreadStatic]
            public static CancellationToken currentCancellationToken;

            

            public static CancellationToken CurrentCancellationToken
            {
                get
                {
                    return currentCancellationToken;
                }
                 
            }

            public CancellableTask(CancellationToken token, Action action)
                : base(() => { CancellableTask.currentCancellationToken = token; action(); }, token)
            {
             
            }
        }

    static void Main(string[] args)
            {
                CancellationTokenSource cancel = new CancellationTokenSource();

               Task task = new CancellableTask(cancel.Token, MyTaskBody);
        

                task.Start();

                Console.ReadLine();
                cancel.Cancel();
                task.Wait();
            }

            private static void MyTaskBody()
            {
                while (true)
                {
                    CancellableTask.CurrentCancellationToken.ThrowIfCancellationRequested();
                    Thread.Sleep(1000);
                    Console.Write(".");
                }
            }


    If you don't create a CancellableTask and just a normal Task then no exception is ever thrown, and you wait for ever

     static void Main(string[] args)
            {
                CancellationTokenSource cancel = new CancellationTokenSource();

                //Task task = new CancellableTask(cancel.Token, MyTaskBody);
                Task task = new Task(MyTaskBody);

                task.Start();

                Console.ReadLine();
                cancel.Cancel();
                task.Wait();
            }




  • Saturday, October 24, 2009 1:58 AMDanny ShihMSFTUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Hi Marc and Andy,

    Thanks for the feedback!

    Re: Why isn't the CancellationToken accessible via the Task object?
    A TaskCompletionSource<TResult>.Task can enter the Canceled state via the SetCanceled method.  Continuation Tasks enter the Canceled state if they are created with one of the NotOn* or OnlyOn* options, and they don't need to run.  In both cases, a Task can become Canceled without its CancellationToken being signaled.  We removed the Task.CancellationToken property to avoid this inconsistency.  We also figured that the token would be in scope (and accessible to the Task delegate) for most scenarios, and on the occasion that it's not, the state object can be used, as you suggested.

    Re: Is it necessary to pass the CancellationToken to the Task object every time?
    It depends on your scenario.  When a Task throws an OperationCanceledException, the following must be true for the Task to enter the Canceled state:
    (1) The OCE contains the CancellationToken that is associated with the Task - in other words, the token that was passed to the API that created the Task.
    (2) That CancellationToken is signaled; its IsCancellationRequested property is true.

    If any of those conditions is false, the Task will transition to the Faulted state.  So as per (1), you do need to pass the token if you want the Task to terminate in the Canceled state.  However, that's not always necessary.  In a lot of scenarios, you can check for cancellation requests and simply return out of the Task delegate.  Note that the Task will terminate in the RanToCompletion state, though.  This can become an issue if you have logic that depends on Task states, e.g. using continuation Tasks that were created with a NotOn* or OnlyOn* option.

    Hope this helps,
    Danny