【问题标题】:Task.ContinueWith does not work with OnlyOnCanceledTask.ContinueWith 不适用于 OnlyOnCanceled
【发布时间】:2017-08-28 01:29:08
【问题描述】:

在微软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);

这是我完整的 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();
}

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

【问题讨论】:

    标签: c# .net asynchronous task


    【解决方案1】:

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

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

    第二个延续没有执行,因为您必须使用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
    

    第二个延续被调用,因为task.StatusCanceled。这下一个 sn-p 不会触发第二个继续,因为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
    

    如上所述,没有调用第二个延续。让我们通过删除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)
    

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


    当只调用.Cancel()而没有调用.ThrowIfCancellationRequested()时,AggregationException实际上是任务取消成功的一个指标。引用MSDN article

    如果您正在等待转换为Canceled 状态的Task,则会引发System.Threading.Tasks.TaskCanceledException 异常(包装在AggregateException 异常中)。请注意,此异常表示成功取消而不是错误情况。因此,任务的Exception 属性返回null

    这让我得出了一个伟大的结论:

    清单 1-44 是 known error

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

    1. 我回答的第一个 sn-p 是取消任务的有效方法。 OnlyOnCanceled 延续被调用,并且没有抛出异常。
    2. 我回答的第二个 sn-p 也是取消任务的有效方法,但不会调用 OnlyOnCanceled 延续,并且会在 Task.Wait() 处引发 AggregationException 供您处理

    免责声明:两种 sn-ps 都是取消任务的有效方法,但它们可能存在我不知道的行为差异。

    【讨论】:

    • 我已经想知道书中这个奇怪的机制好几个小时了......你的详细解释真的很有帮助!由于t.Exception... 部分,我收到了错误。
    【解决方案2】:

    使用cancellationTokenSource.Cancel() 取消的任务实例将具有TaskStatus.RanToCompletion 状态,而不是TaskStatus.Canceled 状态。所以我认为你必须将TaskContinuationOptions.OnlyOnCanceled 更改为TaskContinuationOptions.OnlyOnRanToCompletion

    您可以在 MSDN 上查看Task Cancellation 了解更多详情。

    这是一个示例代码:

     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.OnlyOnRanToCompletion);
    
    Console.ReadLine();
    cancellationTokenSource.Cancel();
    
    try
        {
            task.Wait();
        }
    catch (AggregateException e)
        {
            foreach (var v in e.InnerExceptions)
                Console.WriteLine(e.Message + " " + v.Message);
        }
    Console.ReadLine();
    

    【讨论】:

    • 我以前也试过这样,但奇怪的是,第二个任务从来没有运行过。控制台上没有打印“您已取消任务”语句。在我的控制台上,只有一个****行,然后“发生一个或多个错误。任务已取消。”,然后当我按Enter时,程序结束。
    • 使用 cancellationTokenSource.Cancel(); 取消的任务实例将具有 TaskStatus.RanToCompletion 状态,而不是 TaskStatus .Canceled 状态。所以我认为你必须将 TaskContinuationOptions.OnlyOnCanceled 更改为 TaskContinuationOptions.OnlyOnRanToCompletion
    • 好的,那么我认为代码本身失去了它的目的。你不知道它是否真的被取消或完成了。想知道为什么微软会让这个错误通过。非常感谢。请编辑您的答案,然后我会标记您的答案。
    【解决方案3】:

    试试这个:

            Task task = Task.Run(() =>
            {
                while (true) {
                    token.ThrowIfCancellationRequested();
                    Console.Write("*");
                    Thread.Sleep(1000);
                }
            }, token).ContinueWith((t) =>
            {
                //t.Exception.Handle((e) => true); //there is no exception
                Console.WriteLine("You have canceled the task");
            }, TaskContinuationOptions.OnlyOnCanceled);
    

    【讨论】:

    • 嗯...我尝试了所有 3 种情况:使用与您的代码相同的代码,仅添加 Throw 行,仅注释 Handle 行,但所有 3 种抛出异常。您能否再次测试并发布您的完整工作 Main 方法?
    猜你喜欢
    • 2016-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-25
    • 1970-01-01
    相关资源
    最近更新 更多