取消任务时的OperationCanceledException与VS TaskCanceledException [英] OperationCanceledException VS TaskCanceledException when task is canceled

查看:203
本文介绍了取消任务时的OperationCanceledException与VS TaskCanceledException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下代码创建一个正在被取消的任务。 await 表达式(第一种情况)抛出 System.OperationCanceledException 而同步 Wait()(情况2)抛出 System.Threading.Tasks.TaskCanceledException (包装在 System.AggregateException 中)。

The following code creates a task which is being canceled. await expression (case 1) throws System.OperationCanceledException while synchronous Wait() (case 2) throws System.Threading.Tasks.TaskCanceledException (wrapped in System.AggregateException).

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Program.MainAsync().Wait();
    }

    private static async Task MainAsync()
    {
        using(var cancellationTokenSource = new CancellationTokenSource())
        {
            var token = cancellationTokenSource.Token;
            const int cancelationCheckTimeout = 100;

            var task = Task.Run(
                async () => 
                {
                    for (var i = 0; i < 100; i++)
                    {
                        token.ThrowIfCancellationRequested();
                        Console.Write(".");
                        await Task.Delay(cancelationCheckTimeout);  
                    }
                }, 
                cancellationTokenSource.Token
            );

            var cancelationDelay = 10 * cancelationCheckTimeout;
            cancellationTokenSource.CancelAfter(cancelationDelay);

            try
            {
                await task; // (1)
                //task.Wait(); // (2) 
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
                Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}");
                Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}");
                Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}");
            }
        }
    }
}

1输出:

..........System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowIfCancellationRequested()
   at Program.<>c__DisplayClass1_0.<<MainAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Program.<MainAsync>d__1.MoveNext()
Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null

情况2输出:

..........System.AggregateException: One or more errors occurred. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled.
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Program.<MainAsync>d__1.MoveNext()
---> (Inner Exception #0) System.Threading.Tasks.TaskCanceledException: A task was canceled.<---

Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null

为什么 System.AggregateException 在第二种情况下不包含 System.OperationCanceledException 作为内部异常?

Why System.AggregateException in the 2nd case doesn't contain System.OperationCanceledException as an inner exception?

我知道 ThrowIfCancellationRequested()抛出 OperationCanceledException ,我们可以看到在两种情况下任务处于取消状态(不是错误状态)。

I know that ThrowIfCancellationRequested() throws OperationCanceledException and we can see that in both cases Task gets to canceled (not faulty) state.

这让我感到困惑,因为从.NET API中取消方法在两种情况下都会产生一致的行为-已取消的任务仅使用系统抛出 TaskCanceledException

This puzzles me because canceling a method from .NET API produces consistent behaviour in both cases - canceled task throws only TaskCanceledException:

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Program.MainAsync().Wait();
    }

    private static async Task MainAsync()
    {
        using(var cancellationTokenSource = new CancellationTokenSource())
        {
            var token = cancellationTokenSource.Token;

            var task = Task.Delay(1000, token);
            cancellationTokenSource.CancelAfter(100);

            try
            {
                await task; // (1)
                //task.Wait(); // (2)
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
                Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}");
                Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}");
                Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}");
            }
        }
    }
}

1输出:

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Program.<MainAsync>d__1.MoveNext()
Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null

情况2输出:

System.AggregateException: One or more errors occurred. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled.
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Program.<MainAsync>d__1.MoveNext()
---> (Inner Exception #0) System.Threading.Tasks.TaskCanceledException: A task was canceled.<---

Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null


推荐答案

此处的区别来自使用 token.ThrowIfCancellationRequested()。此方法检查是否取消,如果请求,则专门抛出 OperationCanceledException 而不是 TaskCanceledException (可理解为 CancellationToken 并非TPL专有)。您可以查看参考源并看到它在调用此方法:

The difference here comes from using token.ThrowIfCancellationRequested(). This method checks for cancellation and if requested throws OperationCanceledException specifically and not TaskCanceledException (understandable as CancellationToken isn't exclusive to the TPL). You can look at the reference source and see that it calls this method:

private void ThrowOperationCanceledException()
{
    throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this);
}

常规取消,尽管确实会产生 TaskCanceledException 。您可以看到,通过取消令牌 before ,任务有机会开始运行:

"Regular" cancellation though will indeed generate a TaskCanceledException. You can see that by cancelling the token before the task had a chance to start running:

cancellationTokenSource.Cancel();
var task = Task.Run(() => { }, cancellationTokenSource.Token);
try
{
    await task; 
}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
    Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}");
    Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}");
    Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}");
}

输出:

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Sandbox.Program.<MainAsync>d__1.MoveNext()
Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null

传统.Net方法通常不使用 CancellationToken.ThrowIfCancellationRequested 对于异步API,因为这仅在将工作卸载到另一个线程时才适用。这些方法用于固有的异步操作,因此可以使用 CancellationToken.Register (或内部 InternalRegisterWithoutEC )监视取消操作。

Traditional .Net methods usually don't use CancellationToken.ThrowIfCancellationRequested for async API as this is only appropriate when offloading work to another thread. These methods are for inherently asynchronous operations so cancellation is monitored using CancellationToken.Register (or the internal InternalRegisterWithoutEC).

这篇关于取消任务时的OperationCanceledException与VS TaskCanceledException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆