5

在这种情况下,预期的是,如果用户通过按 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();
    }
}
4

2 回答 2

7

让我们从几个事实开始:

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

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

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

解决方案:

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

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);
于 2014-06-22T05:54:41.127 回答
4

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

于 2014-06-22T00:17:44.920 回答