CancellationTokenSource的行为不符合预期

时间:2014-06-21 23:16:14

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

在这种情况下预期的是,如果用户通过按Enter键取消任务,ContinueWith挂钩的其他任务将会运行,但事实并非如此,按照尽管AggregateException中显然没有执行明确的处理,但ContinueWith仍然被抛出 有关以下的任何说明吗?

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();
    }
}

2 个答案:

答案 0 :(得分:7)

让我们先从几个事实开始:

  1. 当您将CancellationToken作为Task.Run的参数传递时,如果在任务开始运行之前取消,则只会产生效果。如果任务已在运行,则将被取消。
  2. 要在> 之后取消任务,您需要使用CancellationToken.ThrowIfCancellationRequested,而不是CancellationToken.IsCancellationRequested
  3. 如果任务被取消,则其Exception属性不会包含任何例外情况且为null
  4. 如果继续任务由于某种原因未运行,则表示已取消。
  5. 任务包含来自其自身及其所有子任务的异常(因此,AggregateException)。
  6. 所以这就是你的代码中发生的事情:

    任务开始运行,因为令牌未被取消。它将一直运行,直到令牌被取消。在结束后,将不会继续运行,因为它仅在前一个任务被取消时运行,而它还没有运行。当你Wait任务时,它会抛出AggregateException TaskCanceledException,因为延续被取消了(如果你删除该延续,则异常将消失)

    解决方案:

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

    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);
    

答案 1 :(得分:4)

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