将取消令牌提供给任务不会执行任何操作

本文关键字:执行 任何 操作 任务 取消 令牌 | 更新日期: 2023-09-27 18:19:17

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

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.canceltoken(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);

要在"飞行途中"取消任务,您需要任务本身观察令牌并在发出取消信号时抛出,类似于:

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

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

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

我首先要说(与对接受答案的评论相反(,MSDN 上Microsoft的例子很糟糕。除非您已经知道取消的工作原理,否则它们不会对您有很大帮助。这篇 MSDN 文章向您展示如何将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;