【问题标题】:Cancellation token in Task constructor: why?任务构造函数中的取消标记:为什么?
【发布时间】:2020-08-18 21:00:41
【问题描述】:

某些System.Threading.Tasks.Task 构造函数将CancellationToken 作为参数:

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

对此我感到困惑的是,没有办法从方法体内部实际获取传入的令牌(例如,没有像Task.CurrentTask.CancellationToken 这样的东西)。令牌必须通过其他机制提供,例如状态对象或在 lambda 中捕获。

那么在构造函数中提供取消令牌的目的是什么?

【问题讨论】:

    标签: c# .net-4.0 task-parallel-library cancellation-token


    【解决方案1】:

    CancellationToken 传递给Task 构造函数会将其与任务相关联。

    引用Stephen Toub's answer from MSDN:

    这有两个主要好处:

    1. 如果令牌在Task 开始执行之前请求取消,Task 将不会执行。而不是过渡到 Running,它将立即转换为 Canceled。这避免了 运行任务的成本(如果它在运行时被取消) 无论如何。
    2. 如果任务主体也在监视取消令牌并抛出包含该令牌的OperationCanceledException (这是ThrowIfCancellationRequested 所做的),然后当任务 看到 OperationCanceledException,它检查 OperationCanceledException 的令牌是否与任务的 令牌。如果是,则该异常被视为对 合作取消和Task 转换为Canceled 状态(而不是 Faulted 状态)。

    【讨论】:

      【解决方案2】:

      构造函数在内部使用令牌进行取消处理。如果您的代码想要访问令牌,您有责任将其传递给自己。我强烈推荐阅读Parallel Programming with Microsoft .NET book at CodePlex

      书中CTS的用法示例:

      CancellationTokenSource cts = new CancellationTokenSource();
      CancellationToken token = cts.Token;
      
      Task myTask = Task.Factory.StartNew(() =>
      {
          for (...)
          {
              token.ThrowIfCancellationRequested();
      
              // Body of for loop.
          }
      }, token);
      
      // ... elsewhere ...
      cts.Cancel();
      

      【讨论】:

      • 如果不将令牌作为参数传递会怎样?看起来行为将是相同的,没有目的。
      • @sergdev:您传递令牌以将其注册到任务和调度程序。不传递它并使用它将是未定义的行为。
      • @sergdev:经过测试:当您不将令牌作为参数传递时,myTask.IsCanceled 和 myTask.Status 不一样。状态将失败而不是取消。尽管如此,异常是相同的:在这两种情况下都是 OperationCanceledException。
      • 如果我不打电话给token.ThrowIfCancellationRequested();怎么办?在我的测试中,行为是相同的。有什么想法吗?
      • @CobaltBlue:when cts.Cancel() is called the Task is going to get canceled and end, no matter what you do 不。如果任务在它开始之前被取消,它是Canceled。如果任务的主体根本不检查任何令牌,它将运行到完成,从而导致 RanToCompletion 状态。如果身体抛出OperationCancelledException,例如通过ThrowIfCancellationRequested,然后Task 将检查该异常的CancellationToken 是否与与Task 关联的相同。如果是,则任务已取消。如果不是,则为 Faulted
      【解决方案3】:

      取消并不像许多人想象的那样简单。 msdn 上的这篇博文解释了其中的一些微妙之处:

      例如:

      在并行扩展和其他系统中的某些情况下,它 出于非到期原因需要唤醒阻塞的方法 由用户明确取消。例如,如果一个线程是 由于集合为空,在 blockingCollection.Take() 上被阻止 另一个线程随后调用 blockingCollection.CompleteAdding(),那么第一个电话应该叫醒 向上并抛出一个InvalidOperationException 表示不正确 用法。

      Cancellation in Parallel Extensions

      【讨论】:

        【解决方案4】:

        这是一个代码示例,演示了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;
                    }
                }
            }
        }
        

        输出:

        *********************************************************************
        * 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!!!
        

        【讨论】:

          猜你喜欢
          • 2015-05-12
          • 1970-01-01
          • 2019-09-14
          • 2019-01-22
          • 2011-10-09
          • 2019-12-05
          • 2018-09-18
          • 2018-09-04
          相关资源
          最近更新 更多