以下代码创建一个正在取消的任务. 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.<<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作为内部异常?
我知道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.<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独有的).您可以查看
reference source并看到它调用此方法:
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.<MainAsync>d__1.MoveNext() Task.IsCanceled: True Task.IsFaulted: False Task.Exception: null
传统的.Net方法通常不对异步API使用CancellationToken.ThrowIfCancellationRequested,因为这仅适用于将工作卸载到另一个线程.这些方法用于固有的异步操作,因此使用CancellationToken.Register(或内部InternalRegisterWithoutEC)监视取消.