none
Tasks and Cancellation Token.

    Question

  • Hi everyone, I have a question on this code.

    This is adapted from a book I'm reading on Parallel Extensions.

    This is the important bit...

            private void cmdStart_Click(object sender, EventArgs e)
            {
                tokenSource = new CancellationTokenSource();
                CancellationToken token = tokenSource.Token;

                // runs a task, a lightweight thread
                Task.Factory.StartNew(() => {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    while (!token.IsCancellationRequested)
                    {
                        DisplayTime display = OnDisplayTime;
                        lblTime.Invoke(display, new[] { watch.Elapsed.ToString() });
                    }
                }, token);
            }

     

    Ok so the task is created and ran with the StartNew() method and the cancellation token is passed in as parameter to StartNew. The cancellation token is used to determine if the loop should stop.

    That's all good but what is the point of passing in the token to the StartNew() method?

    This code works just as good...

            private void cmdStart_Click(object sender, EventArgs e)
            {
                tokenSource = new CancellationTokenSource();
                CancellationToken token = tokenSource.Token;

                // runs a task, a lightweight thread
                Task.Factory.StartNew(() => {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    while (!token.IsCancellationRequested)
                    {
                        DisplayTime display = OnDisplayTime;
                        lblTime.Invoke(display, new[] { watch.Elapsed.ToString() });
                    }
                });    // NO TOKEN PASSED IN TO START NEW
            }

    You see the token that's used is defined in cmdStart....

     

    So I guess my question is how do you get to the token that's passed in to the StartNew() method from within the task?  How do I get this code to run?

     

            private void cmdStart_Click(object sender, EventArgs e)
            {
                tokenSource = new CancellationTokenSource();
                Task.Factory.StartNew(() =>
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    while (???????)  // I want the token passed in?
                    {
                        DisplayTime display = OnDisplayTime;
                        lblTime.Invoke(display, new[] { watch.Elapsed.ToString() });
                    }
                }, tokenSource.Token );
            }

    I thought it might have been an argument to the lambda statement but it's not.... for example this doesn't work.

     

    // DOES NOT WORK

            private void cmdStart_Click(object sender, EventArgs e)
            {
                tokenSource = new CancellationTokenSource();
                Task.Factory.StartNew((CancellationToken token) =>
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    while (!token.IsCancellationRequested)
                    {
                        DisplayTime display = OnDisplayTime;
                        lblTime.Invoke(display, new[] { watch.Elapsed.ToString() });
                    }
                }, tokenSource.Token);
            }

    Anyone help me out?  Thanks for your help.

     

     

     

     

     


    …we each have more potential than we might ever presume to guess. (Blog: http://dsmyth.blogspot.com/)
    Tuesday, November 02, 2010 12:46 PM

Answers

  •  

    So what your saying is the cancellatoin token passed to StartNew isn't for cancelling a running task at all, it's for preventing waiting task from starting (because say a parent task has been cancelled)?

     

    Basically, yes.  It's more a way to cancel a task that's scheduled to occur, but not started yet.  Once the task is running, the only way to cancel it is cooperatively via your own checking within the method.  Without this, you'd have to always start the task, then check it internally, which would add a lot of extra, unnecessary overhead.

     

    Having the running task be canceled from an external source, btw, would be a bad thing - it's basically like calling Thread.Abort(), which is something you almost never want to do.  CancellationToken(&Source) allow a nice, clean cooperative model of cancelation.

     

     


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    • Marked as answer by Derek Smyth Tuesday, November 02, 2010 9:29 PM
    Tuesday, November 02, 2010 6:17 PM
    Moderator

All replies

  • Hi Derek,

    You don't need to retrieve the Token from your current Task, because the your Token has been captured by your lambda.

    We need to pass the Token as parameter to StartNew  because inside the task internal code to consider if the Token has been cancelled or not and treat the behavior with correctness to follow.

    I hope it is useful

    Bruno

     


    Boucard Bruno - http://blogs.msdn.com/b/devpara/

    Tuesday, November 02, 2010 3:00 PM
  • There are two separate use cases for the CalcellationToken here - and I think that's what's causing the confusion.

    I'll try to adapt your code to make it more understandable...

     

    Suppose you have this:

    // Defined at the class level
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    
    // Now, say you're doing something in a timer's tick event, not a button, as a "one time" long running operation
     private void timer_Tick(object sender, EventArgs e)
        {
          timer.Enabled = false;
          var token = tokenSource.Token;
          
          Task.Factory.StartNew(() =>
          {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            // Use Case 1
            while (!token.IsCancellationRequested)
            {
              DisplayTime display = OnDisplayTime;
              lblTime.Invoke(display, new[] { watch.Elapsed.ToString() });
              Thread.Sleep(250);
            }
          }, token); // Use Case 2
        }

    And then, elsewhere, you have a button that sets the token source to canceled.

     

    Here, the token is being used in 2 cases (commented), for two different purposes.

     

    In "use case 1", you're capturing the token via a closure when you create the lambda expression, and checking this token as part of your logic in order to support cancellation within your delegate.

     

    In "use case 2", however, the scheduler itself is being passed the token - not you.  Depending on the current task scheduler, "Task.Factory.StartNew" may not start the task immediately.  Remember, even the default task scheduler uses the thread pool, so if you have a  lot of other tasks running, it may be a while before an available threadpool thread is ready to be scheduled.  In this case, the token being passed to the StartNew method on TaskFactory allows the task to be cancelled before it's ever started, preventing your code from even beginning to run.

     

    Basically, the two cases are for completely different operations....

     

     

     


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    Tuesday, November 02, 2010 3:31 PM
    Moderator
  • Hey Reed. thanks for the time that must have taken.

    That went through my mind, that the token being passed in to StartNew was for a scheduler and that the task cancellation management would happen seperately from the task.

     

            private void cmdStart_Click(object sender, EventArgs e)
            {
                tokenSource = new CancellationTokenSource();
                Task.Factory.StartNew(() =>
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    while (true)
                    {
                        DisplayTime display = OnDisplayTime;
                        lblTime.Invoke(display, new[] { watch.Elapsed.ToString() });
                    }
                }, tokenSource.Token);
            }

    In other words the task would run until Cancel is called; then an OperationCancelledException is thrown by the scheduler for each task that's running causing all tasks (with that token) to stop.

    That was what went through my mind; but it doesn't work either.

     

    So what your saying is the cancellatoin token passed to StartNew isn't for cancelling a running task at all, it's for preventing waiting task from starting (because say a parent task has been cancelled)?

     

     

     


    …we each have more potential than we might ever presume to guess. (Blog: http://dsmyth.blogspot.com/)
    Tuesday, November 02, 2010 5:41 PM
  •  

    So what your saying is the cancellatoin token passed to StartNew isn't for cancelling a running task at all, it's for preventing waiting task from starting (because say a parent task has been cancelled)?

     

    Basically, yes.  It's more a way to cancel a task that's scheduled to occur, but not started yet.  Once the task is running, the only way to cancel it is cooperatively via your own checking within the method.  Without this, you'd have to always start the task, then check it internally, which would add a lot of extra, unnecessary overhead.

     

    Having the running task be canceled from an external source, btw, would be a bad thing - it's basically like calling Thread.Abort(), which is something you almost never want to do.  CancellationToken(&Source) allow a nice, clean cooperative model of cancelation.

     

     


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    • Marked as answer by Derek Smyth Tuesday, November 02, 2010 9:29 PM
    Tuesday, November 02, 2010 6:17 PM
    Moderator
  • It's more a way to cancel a task that's scheduled to occur, but not started yet.  Once the task is running, the only way to cancel it is cooperatively via your own checking within the method.  Without this, you'd have to always start the task, then check it internally, which would add a lot of extra, unnecessary overhead

    Thanks again Reed. I get it. Cancel a task that's scheduled to run but not started yet; like a continuation task as an example.

    Going to code up an example to test it out and will post it up tomorrow for anyone else.

    Nice one; Cheers Reed.


    …we each have more potential than we might ever presume to guess. (Blog: http://dsmyth.blogspot.com/)
    Tuesday, November 02, 2010 9:29 PM
  • Here is the code example....

     

    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using System.Threading;
    
    namespace CancellationOfPendingTasks
    {
      class Program
      {
        static void Main(string[] args)
        {
          CancellationTokenSource source = new CancellationTokenSource();
    
          Task<Double[]> init = new Task<Double[]>(InitialiseTask);
    
          Task<Double> sum = init.ContinueWith<Double>(SumTask, source.Token);
          sum.ContinueWith((Task<Double> previous) =>
                { Console.Out.WriteLine("Sum: {0}", previous.Result); }, 
                TaskContinuationOptions.NotOnCanceled);
    
          // continuations on a cancelled tasks are still ran unless TaskContinuationOptions is used.
    
          Task<Double> average = init.ContinueWith<Double>(AverageTask, source.Token);
          average.ContinueWith((Task<Double> previous) =>
                { Console.Out.WriteLine("Average: {0}", previous.Result); },
                TaskContinuationOptions.NotOnCanceled);
    
          init.Start();
    
          Random rnd = new Random();
          int probability = rnd.Next(100);
          if (probability < 75)
            source.Cancel();
    
    
          init.Wait();
    
          Console.Out.WriteLine("Random Data Generation task cancelled: {0}", init.IsCanceled);
          Console.Out.WriteLine("Sum task cancelled: {0}", sum.IsCanceled);
          Console.Out.WriteLine("Average task cancelled: {0}", average.IsCanceled);
          
          Console.In.ReadLine();
        }
    
        static Double[] InitialiseTask()
        {
          // this will take time, calling cancellation will not stop list from being generated
          Console.Out.WriteLine("Producing random data.");
    
          Random rnd = new Random();
          Double[] data = Enumerable.Range(0, 100000).Select((index) => rnd.NextDouble()).ToArray();
    
          Console.Out.WriteLine("Random data created.");
    
          return data;
        }
    
        static Double SumTask(Task<Double[]> previous)
        {
          return previous.Result.Sum();
        }
    
        static Double AverageTask(Task<Double[]> previous)
        {
          return previous.Result.Average();
        }
    
      }
    
    }
    
    


    …we each have more potential than we might ever presume to guess. (Blog: http://dsmyth.blogspot.com/)
    Wednesday, November 03, 2010 9:40 AM