CancellationTokenSource的行为不符合预期

本文关键字:不符合 CancellationTokenSource | 更新日期: 2023-09-27 18:09:36

在这种情况下所期望的是,如果用户通过按enter键取消任务,则ContinueWith所钩子的另一个任务将运行,但事实并非如此,尽管ContinueWith中显式处理显然没有被执行,但AggregateException仍然被抛出。
请澄清一下以下内容?

class Program
{
    static void Main(string[] args)
    {
        CancellationTokenSource tokensource = new CancellationTokenSource();
        CancellationToken token = tokensource.Token;
        Task task = Task.Run(() =>
        {
            while (!token.IsCancellationRequested)
            {
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            t.Exception.Handle((e) => true);
            Console.WriteLine("You have canceled the task");
        }, TaskContinuationOptions.OnlyOnCanceled);
        Console.WriteLine("Press any key to cancel");
        Console.ReadLine();
        tokensource.Cancel();
        task.Wait();
    }
}

CancellationTokenSource的行为不符合预期

让我们从一些事实开始:

  1. 当你传递一个CancellationToken作为Task.Run的参数时,它只有在任务开始运行之前取消才有效果。如果任务已经在运行,它将而不是被取消。
  2. 在开始运行后,要取消任务,您需要使用CancellationToken.ThrowIfCancellationRequested,而不是CancellationToken.IsCancellationRequested
  3. 如果一个任务被取消,它的Exception属性不包含任何异常,并且是null
  4. 如果延续任务由于某种原因没有运行,这意味着它被取消了。
  5. 一个任务包含来自它自己和它的所有子任务的异常(因此,AggregateException)。

这就是在你的代码中发生的事情:

任务开始运行,因为令牌没有被取消。它将一直运行,直到令牌被取消。在它结束之后,continuation 将不会运行,因为它只在前一个任务被取消时运行,并且它还没有被取消。当您Wait任务时,它将抛出AggregateExceptionTaskCanceledException,因为延续被取消(如果您将删除该延续,异常将消失)。

解决方案:

您需要修复任务,以便它实际上被取消,并删除(或null检查)异常处理,因为没有异常:

var task = Task.Run(new Action(() =>
{
    while (true)
    {
        token.ThrowIfCancellationRequested();
        Console.Write("*");
        Thread.Sleep(1000);
    }
}), token).ContinueWith(
    t => Console.WriteLine("You have canceled the task"),
    TaskContinuationOptions.OnlyOnCanceled);

如果将令牌作为第二个参数传递,则任务将无法顺利继续,因为它实际上已经被取消了。相反,它抛出OperationCanceledException,该异常被包装在AggregateException中。这完全在意料之中。现在,如果你没有将令牌传递给任务构造函数,那么你会看到你所期望的行为,因为因为你只会使用令牌作为退出while循环的标志。在这种情况下,您并没有真正取消任务,而是退出while循环并正常完成任务。