将取消令牌提供给任务什么都不做?

Feeding a cancellationtoken to a Task does nothing?

我有两个例子,直接来自微软,这些例子似乎与取消令牌无关,因为我可以删除提供给任务的令牌,结果是一样的。所以我的问题是:什么是取消令牌,为什么是糟糕的例子?我错过了什么..? :)

using System;
using System.Threading;
using System.Threading.Tasks;
namespace Chapter1.Threads
{
    public class Program
    {
        static void Main()
        {
            CancellationTokenSource cancellationTokenSource =
                new CancellationTokenSource();
            CancellationToken token = cancellationTokenSource.Token;
            Task task = Task.Run(() =>
            {
                while (!token.IsCancellationRequested)
                {
                    Console.Write(“*”);
                    Thread.Sleep(1000);
                }
                token.ThrowIfCancellationRequested();
            }, token);
            try
            {
                Console.WriteLine(“Press enter to stop the task”);
                Console.ReadLine();
                cancellationTokenSource.Cancel();
                task.Wait();
            }  
            catch (AggregateException e)
            {
                Console.WriteLine(e.InnerExceptions[0].Message);
            }
            Console.WriteLine(“Press enter to end the application”);
            Console.ReadLine();
        }
    }
}

代码示例2: https://msdn.microsoft.com/en-us/library/system.threading.cancellationtoken(v=vs.110).aspx

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      // Define the cancellation token.
      CancellationTokenSource source = new CancellationTokenSource();
      CancellationToken token = source.Token;

      Random rnd = new Random();
      Object lockObj = new Object();

      List<Task<int[]>> tasks = new List<Task<int[]>>();
      TaskFactory factory = new TaskFactory(token);
      for (int taskCtr = 0; taskCtr <= 10; taskCtr++) {
         int iteration = taskCtr + 1;
         tasks.Add(factory.StartNew( () => {
                                       int value;
                                       int[] values = new int[10];
                                       for (int ctr = 1; ctr <= 10; ctr++) {
                                          lock (lockObj) {
                                             value = rnd.Next(0,101);
                                          }
                                          if (value == 0) { 
                                             source.Cancel();
                                             Console.WriteLine("Cancelling at task {0}", iteration);
                                             break;
                                          }   
                                          values[ctr-1] = value; 
                                       }
                                       return values;
                                    }, token));   

      }
      try {
         Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(), 
                                                      (results) => {
                                                         Console.WriteLine("Calculating overall mean...");
                                                         long sum = 0;
                                                         int n = 0; 
                                                         foreach (var t in results) {
                                                            foreach (var r in t.Result) {
                                                                  sum += r;
                                                                  n++;
                                                               }
                                                         }
                                                         return sum/(double) n;
                                                      } , token);
         Console.WriteLine("The mean is {0}.", fTask.Result);
      }   
      catch (AggregateException ae) {
         foreach (Exception e in ae.InnerExceptions) {
            if (e is TaskCanceledException)
               Console.WriteLine("Unable to compute mean: {0}", 
                                 ((TaskCanceledException) e).Message);
            else
               Console.WriteLine("Exception: " + e.GetType().Name);
         }
      }
      finally {
         source.Dispose();
      }
   }
}

因为 .Net 中的取消是协作将 CancellationToken 传递给 Task.Run 不足以确保任务被取消。

将令牌作为参数传递只会将令牌与任务相关联。它只能取消任务 如果它在令牌被取消之前没有机会开始 运行 。例如:

var token = new CancellationToken(true); // creates a cancelled token
Task.Run(() => {}, token);

要取消任务 "mid-flight" 你需要任务本身来观察令牌并在发出取消信号时抛出,类似于:

Task.Run(() => 
{
    while (true)
    {
        token.ThrowIfCancellationRequested();
        // do something
    }
}, token);

此外,简单地从任务内部抛出异常只会将任务标记为Faulted。要将其标记为 Cancelled TaskCanceledException.CancellationToken 需要匹配传递给 Task.Run.

的标记

我正要问一个类似的问题,直到我找到了这个问题。 i3arnon 的回答是有道理的,但我会添加这个答案作为补充,希望能帮助别人。

我首先要说(与接受的答案的评论相反)MSDN 上 Microsoft 的示例很糟糕。除非您已经知道 Cancellation 的工作原理,否则它们对您的帮助不大。 This MSDN article 向您展示了如何将 CancellationToken 传递给 Task,但是如果您按照示例进行操作,它们实际上不会向您展示如何取消 您自己的 当前正在执行 TaskCancellationToken 消失在 Microsoft 代码中:

  • await client.GetAsync("http://msdn.microsoft.com/en-us/library/dd470362.aspx", ct);
  • await response.Content.ReadAsByteArrayAsync();

以下是我如何使用 CancellationToken:

的示例

当我有一个需要不断重复的任务时:

public class Foo
{
    private CancellationTokenSource _cts;

    public Foo()
    {
        this._cts = new CancellationTokenSource();
    }

    public void StartExecution()
    {
        Task.Factory.StartNew(this.OwnCodeCancelableTask, this._cts.Token);
        Task.Factory.StartNew(this.OwnCodeCancelableTask_EveryNSeconds, this._cts.Token);
    }

    public void CancelExecution()
    {
        this._cts.Cancel();
    }

    /// <summary>
    /// "Infinite" loop with no delays. Writing to a database while pulling from a buffer for example.
    /// </summary>
    /// <param name="taskState">The cancellation token from our _cts field, passed in the StartNew call</param>
    private void OwnCodeCancelableTask(object taskState)
    {
        var token = (CancellationToken) taskState;

        while ( !token.IsCancellationRequested )
        {
            Console.WriteLine("Do your task work in this loop");
        }
    }


    /// <summary>
    /// "Infinite" loop that runs every N seconds. Good for checking for a heartbeat or updates.
    /// </summary>
    /// <param name="taskState">The cancellation token from our _cts field, passed in the StartNew call</param>
    private async void OwnCodeCancelableTask_EveryNSeconds(object taskState)
    {
        var token = (CancellationToken)taskState;

        while (!token.IsCancellationRequested)
        {
            Console.WriteLine("Do the work that needs to happen every N seconds in this loop");

            // Passing token here allows the Delay to be cancelled if your task gets cancelled.
            await Task.Delay(1000 /*Or however long you want to wait.*/, token);
        }
    }
}

当我有一个用户可以启动的任务时:

public class Foo
{
    private CancellationTokenSource _cts;
    private Task _taskWeCanCancel;

    public Foo()
    {
        this._cts = new CancellationTokenSource();

        //This is where it's confusing. Passing the token here will only ensure that the task doesn't
        //run if it's canceled BEFORE it starts. This does not cancel the task during the operation of our code.
        this._taskWeCanCancel = new Task(this.FireTheTask, this._cts.Token);
    }
    /// <summary>
    /// I'm not a fan of returning tasks to calling code, so I keep this method void
    /// </summary>
    public void FireTheTask()
    {
        //Check task status here if it's required.
        this._taskWeCanCancel.Start();
    }

    public void CancelTheTask()
    {
        this._cts.Cancel();
    }

    /// <summary>
    /// Go and get something from the web, process a piece of data, execute a lengthy calculation etc...
    /// </summary>
    private async void OurTask()
    {
        Console.WriteLine("Do your work here and check periodically for task cancellation requests...");

        if (this._cts.Token.IsCancellationRequested) return;

        Console.WriteLine("Do another step to your work here then check the token again if necessary...");

        if (this._cts.Token.IsCancellationRequested) return;

        Console.WriteLine("Some work that we need to delegate to another task");

        await Some.Microsoft.Object.DoStuffAsync();
    }

}

也许我错过了 Task 的一些关键特性,但是将 CancellationToken 传递给 Task 因为除了状态之外的任何东西对我来说都没有多大意义。我还没有 运行 进入 CancellationToken 传递给 Task 并在 运行 之前取消 Task 的情况,即使我确实,我创建的每个任务的第一行总是

if (token.IsCancellationRequested) return;