Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OperationCanceledException VS TaskCanceledException when task is canceled

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

Case 1 output:

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

Case 2 output:

..........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 System.AggregateException in the 2nd case doesn't contain System.OperationCanceledException as an inner exception?

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

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

Case 1 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 Program.<MainAsync>d__1.MoveNext() Task.IsCanceled: True Task.IsFaulted: False Task.Exception: null 

Case 2 output:

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 
like image 645
Bojan Komazec Avatar asked Dec 18 '15 15:12

Bojan Komazec


People also ask

How can you handle a canceled task?

Instead of canceling your running task and catching exception to show user what has really happened, you can continue with another task only if the previous task has been cancelled. Make use of Continue With method which takes the returned value from the cancelled task as a parameter.

What is task Cancelled exception?

There's 2 likely reasons that a TaskCanceledException would be thrown: Something called Cancel() on the CancellationTokenSource associated with the cancellation token before the task completed. The request timed out, i.e. didn't complete within the timespan you specified on HttpClient. Timeout .

Which object do you inspect to determine if a long running task will be Cancelled?

For canceling, we use CancellationTokenSource object.

What is the way to create a task that can be Cancelled after it starts?

Create and start a cancelable task. Pass a cancellation token to your user delegate and optionally to the task instance. Notice and respond to the cancellation request in your user delegate. Optionally notice on the calling thread that the task was canceled.


2 Answers

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

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

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

like image 189
i3arnon Avatar answered Sep 30 '22 18:09

i3arnon


TaskCanceledException inherits from OperationCanceledException. So it least there is a little consitency.

if( ex is OperationCanceledException) { ... } 
like image 38
Tomas Kubes Avatar answered Sep 30 '22 17:09

Tomas Kubes