以下代码创建一个正在取消的任务.await
表达式(案例1)抛出System.OperationCanceledException
同步Wait()
(案例2)抛出System.Threading.Tasks.TaskCanceledException
(包装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.<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. 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.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
内部异常?
我知道ThrowIfCancellationRequested()
抛出OperationCanceledException
,我们可以看到在两种情况下Task
都被取消(没有错误)状态.
这让我很困惑,因为从.NET API中取消一个方法会在两种情况下产生一致的行为 - 仅取消任务抛出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.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.d__1.MoveNext() ---> (Inner Exception #0) System.Threading.Tasks.TaskCanceledException: A task was canceled.<--- Task.IsCanceled: True Task.IsFaulted: False Task.Exception: null
i3arnon.. 12
这里的区别来自于使用token.ThrowIfCancellationRequested()
.此方法检查是否取消,如果请求OperationCanceledException
具体而非抛出TaskCanceledException
(可理解为CancellationToken
不属于TPL).您可以查看参考源并看到它调用此方法:
private void ThrowOperationCanceledException() { throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this); }
"定期"取消虽然确实会产生一个TaskCanceledException
.您可以通过在任务有机会开始运行之前取消令牌来查看:
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.d__1.MoveNext() Task.IsCanceled: True Task.IsFaulted: False Task.Exception: null
传统的.Net方法通常不CancellationToken.ThrowIfCancellationRequested
用于异步API,因为这仅适用于将工作卸载到另一个线程.这些方法用于固有的异步操作,因此使用CancellationToken.Register
(或内部InternalRegisterWithoutEC
)监视取消.
这里的区别来自于使用token.ThrowIfCancellationRequested()
.此方法检查是否取消,如果请求OperationCanceledException
具体而非抛出TaskCanceledException
(可理解为CancellationToken
不属于TPL).您可以查看参考源并看到它调用此方法:
private void ThrowOperationCanceledException() { throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this); }
"定期"取消虽然确实会产生一个TaskCanceledException
.您可以通过在任务有机会开始运行之前取消令牌来查看:
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.d__1.MoveNext() Task.IsCanceled: True Task.IsFaulted: False Task.Exception: null
传统的.Net方法通常不CancellationToken.ThrowIfCancellationRequested
用于异步API,因为这仅适用于将工作卸载到另一个线程.这些方法用于固有的异步操作,因此使用CancellationToken.Register
(或内部InternalRegisterWithoutEC
)监视取消.