【问题标题】:CancellationTokenSource not behaving as expectedCancellationTokenSource 未按预期运行
【发布时间】:2014-06-21 23:16:14
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    让我们从几个事实开始:

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

    这就是您的代码中发生的情况:

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

    解决方案:

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

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

    【讨论】:

    • 根据Microsoft Docs.NET Framework source codeThrowIfCancellationRequested使用IsCancellationRequested属性,所以使用任何一个应该没有区别,关于在之前或之后取消任务任务正在运行。
    • @Alexandre 不同的是ThrowIfCancellationRequested会取消任务而IsCancellationRequested不会。
    • 好吧,当然,IsCancellationRequested 只是一个bool 属性,查看它的值不会引发任何异常。但是答案指出,当您传递 CancellationToken [...] 时,只有在任务开始运行之前取消它才有效。如果任务已经在运行,它不会被取消。要在任务开始运行后取消它,您需要使用 ThrowIfCancellationRequested,而不是 IsCancellationRequested。 关于这一点,两种方式的行为相同:如果 CancellationToken is 取消,两者都会识别它。
    • @Alexandre 您可以使用IsCancellationRequested 收到取消通知并让您的代码完成任务。但是,虽然任务将结束,但它不会被取消。并且 TaskContinuationOptions.OnlyOnCanceled 选项的延续将不会运行。
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-12
      • 1970-01-01
      • 2020-06-28
      • 2012-02-18
      • 2018-01-18
      • 2012-06-14
      • 2019-03-03
      • 2012-09-21
      相关资源
      最近更新 更多