问题描述
以下代码创建一个正在被取消的任务。 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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!