c# - Task.ContinueWith 不适用于 OnlyOnCanceled

标签 c# .net asynchronous task

微软70-483考试引用书CancellationToken的使用,第一种用signal取消的方式是抛异常,然后介绍第二种:

Instead of catching the exception, you can also add a continuation Task that executes only when the Task is canceled. In this Task, you have access to the exception that was thrown, and you can choose to handle it if that’s appropriate. Listing 1-44 shows what such a continuation task would look like

这是 list 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);

这是我完整的主要方法代码:

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 方法。此外,如果我删除该调用,则第二个 Task 永远不会运行(不会抛出异常)。我做错了什么吗?是否可以在不使用 try-catch 的情况下处理异常?

最佳答案

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

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.Status Canceled 调用了第二个延续。下一个片段不会触发第二次继续,因为 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。这表明只是从 token 源调用.Cancel() 不会将任务状态设置为已取消


当只调用.Cancel()而不调用.ThrowIfCancellationRequested()时,AggregationException实际上是任务成功的标志消除。引用MSDN article :

If you are waiting on a Task that transitions to the Canceled state, a System.Threading.Tasks.TaskCanceledException exception (wrapped in an AggregateException exception) is thrown. Note that this exception indicates successful cancellation instead of a faulty situation. Therefore, the task's Exception property returns null.

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

list 1-44 是一个 known error .

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

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

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

关于c# - Task.ContinueWith 不适用于 OnlyOnCanceled,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33156963/

相关文章:

.net - MemoryStream 使用了多少内存?

.net - T-sql、刻度、时间戳

javascript - Promise.all() 中的 then 子句何时运行?

c# - 继承 IComparable<T> 的接口(interface)引用对象的排序列表

.net - 使用 MongoDb 处理迁移

C# 创建目录并设置权限

c# - Autofac:注册异步工厂方法

Javascript:使用 setInterval 同步迭代 for 循环

c# - 有没有办法在不使用后台线程的情况下更新进度条?

c# - System.IO.File.Exists(fpath)在Chrome和Firefox中返回false