Task.ContinueWith不适用于OnlyOnCanceled

Luk*_* Vo 7 .net c# asynchronous task

在微软70-483关于使用CancellationToken的考试的参考书中,取消信号的第一种方法是抛出异常,然后介绍第二种方法:

您还可以添加仅在取消任务时执行的继续任务,而不是捕获异常.在此任务中,您可以访问引发的异常,如果合适,您可以选择处理它.清单1-44显示了这样的延续任务的样子

这是列表1-44:

        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);
Run Code Online (Sandbox Code Playgroud)

这是我的完整Main方法代码:

static void Main(string[] args)
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var token = cancellationTokenSource.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.ReadLine();
    cancellationTokenSource.Cancel();
    task.Wait();

    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

但是,与我所说的不同,当我按Enter时,异常(AggregationException)仍然会在task.Wait()调用时抛出到Main方法.此外,如果我删除该调用,第二个任务永远不会运行(不会抛出异常).有什么我做错了吗?是否可以在不使用的情况下处理异常try-catch

kdb*_*man 7

要明确说明问题,您的第二个延续不执行,但您认为它应该:

static void Main(string[] args)
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var token = cancellationTokenSource.Token;

    Task task = Task.Run(() =>
    {
        while (!token.IsCancellationRequested)
        {
            Console.Write("*");
            Thread.Sleep(1000);
        }
    }, token).ContinueWith((t) =>
    {                                                     //  THIS
        t.Exception.Handle((e) => true);                  //  ISN'T
        Console.WriteLine("You have canceled the task");  //  EXECUTING
    }, TaskContinuationOptions.OnlyOnCanceled);

    Console.ReadLine();
    cancellationTokenSource.Cancel();
    task.Wait();

    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

第二个延续不执行,因为您必须使用token.ThrowIfCancellationRequested()才能触发它:

        Task task = Task.Run(() =>
        {
            while (true)
            {
                token.ThrowIfCancellationRequested();  // <-- NOTICE
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            Console.WriteLine("From Continuation: " + t.Status);

            Console.WriteLine("You have canceled the task");
        }, TaskContinuationOptions.OnlyOnCanceled);

// OUTPUT:
// ***
// From Continuation: Canceled
// You have canceled the task
Run Code Online (Sandbox Code Playgroud)

第二延续称为因为task.StatusCanceled.下一个片段不会触发第二个延续,因为task.Status它未设置为Canceled:

        Task task = Task.Run(() =>
        {
            while (!token.IsCancellationRequested)
            {
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            Console.WriteLine("From Continuation: " + t.Status);

            Console.WriteLine("You have canceled the task");
        }, TaskContinuationOptions.OnlyOnCanceled);

// OUTPUT:
// AggregationException
Run Code Online (Sandbox Code Playgroud)

如上所述,第二个延续没有被称为.让我们通过删除OnlyOnCanceled子句来强制执行:

        Task task = Task.Run(() =>
        {
            while (!token.IsCancellationRequested)
            {
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            Console.WriteLine("From Continuation: " + t.Status);
            Console.WriteLine("You have NOT canceled the task");

        });   // <-- OnlyOnCanceled is gone!

// OUTPUT:
// ***
// From Continuation: RanToCompletion
// You have NOT canceled the task
// (no AggregationException thrown)
Run Code Online (Sandbox Code Playgroud)

请注意,即使.Cancel()被调用,task.Status延续中的from也是RanToCompletion.还要注意没有AggregationException抛出.这表明只是.Cancel()从令牌源调用不会将任务状态设置为Canceled.


当仅.Cancel()调用.ThrowIfCancellationRequested()且未调用时,AggregationException实际上是成功取消任务的指示符.引用MSDN文章:

如果您正在等待Task转换到Canceled状态,则抛出System.Threading.Tasks.TaskCanceledException异常(包含在AggregateException异常中).请注意,此异常表示成功取消而不是出现故障.因此,任务的Exception属性返回null.

这让我得出了一个圆满的结论:

清单1-44是一个已知错误.

t.Exception...我的所有代码都省略了您的行,因为成功取消后" 任务的Exception属性返回null ".清单1-44中应该省略该行.看起来他们正在混淆以下两种技术:

  1. 我的答案的第一个片段是取消任务的有效方法.将OnlyOnCanceled继续被调用,不会引发任何异常.
  2. 我的答案的第二个片段也是取消任务的有效方法,但是OnlyOnCanceled没有调用继续,并且抛出了AggregationException以供您处理Task.Wait()

免责声明:两个片段都是取消任务的有效方法,但它们可能存在我不知道的行为差异.

  • 数小时以来,我一直在想这本书中的这种奇怪机制……您的详细解释真的很有帮助!由于出现“ t.Exception ...”部分,导致出现错误。 (2认同)