OperationCanceledException VS TaskCanceledException when canceling a task

The following code creates a task that is canceled. await expression (case 1) throws a System.OperationCanceledException , while synchronous Wait() (case 2) throws a 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())}"); } } } } 

The output of the 1st case:

 ..........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 

Result 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 

Why does System.AggregateException in the second case not contain a System.OperationCanceledException as an internal exception?

I know that ThrowIfCancellationRequested() throws an OperationCanceledException , and we see that in both cases the Task goes into the cancel state (not erroneous).

This puzzles me because canceling a method from the .NET API provides consistent behavior in both cases - a canceled task throws only a 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())}"); } } } } 

The output of the 1st case:

 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 

Result 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 
+5
source share
1 answer

The difference here is due to the use of token.ThrowIfCancellationRequested() . This method checks for cancellation, and if the request TaskCanceledException OperationCanceledException , not a TaskCanceledException (it is clear that the CancellationToken not exclusive to 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); } 

A "regular" TaskCanceledException , although it will TaskCanceledException a TaskCanceledException . You can see that by canceling the token before , the task had the opportunity to start work:

 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())}"); } 

Output:

 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 

Traditional .Net methods usually do not use CancellationToken.ThrowIfCancellationRequested for async APIs, since this is necessary when loading work into another thread. This method is designed for essentially asynchronous operations, so cancellation tracking is done using CancellationToken.Register (or internal InternalRegisterWithoutEC ).

+5
source

Source: https://habr.com/ru/post/1238649/


All Articles