Showing posts with label TPL. Show all posts
Showing posts with label TPL. Show all posts

Monday, 21 December 2015

OperationCanceledException vs TaskCanceledException when task is canceled


In my previous post, "How to cancel a Task", I mentioned that MSDN recommends throwing OperationCanceledException from a cancelled task. But the following example shows that canceling .NET methods makes them throw a different exception type - TaskCanceledException:

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


So why .NET does not follow its own rules? Why exception thrown in the example above was not OperationCanceledException?

I asked this question on SO and this is how I interpret the answer I got:

(1) System.OperationCanceledException class has been around longer than TPL. MSDN describes it as: "The exception that is thrown in a thread upon cancellation of an operation that the thread was executing." It is intended to be thrown from a thread in which operation is executed. .NET async API are actually synchronous operations (return Task, not marked as async). They don't have additional thread involved which makes OperationCanceledException not appropriate exception to be thrown.

(2) It is different to cancel a Task (setup of the engine which will execute "real work", user's operation) than to cancel operation itself. For example, we can pass already cancelled token to a task or can simply create already cancelled task. Operation and its thread are not started at all in these cases but if we await such task and catch OperationCanceledException we might think they were actually started. So it makes sense to use different type of the exception in these two different scenarios.

Example 1: Already canceled token is passed to task factory method:

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


Example 2: Awaiting task which is created as already canceled:

Output:


System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Program.d__1.MoveNext()
Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null


From the points made above we can conclude that caller of the cancelable task can expect two types of exceptions:OperationCanceledException and TaskCanceledException. As the latter derives from the former, it is enough to handle OperationCanceledException if we want to handle the case of canceled task:




Further reading:
SO Q&A: OperationCanceledException VS TaskCanceledException when task is canceled
SO Q&A: Difference between OperationCanceledException and TaskCanceledException?
Andrew L Arnott: Recommended patterns for CancellationToken

Sunday, 20 December 2015

How to cancel a Task


It is a good practice to offer users a chance to cancel some long-running operation. Such operation usually has to run asynchronously and in a TPL-based design is modeled as a method returning a Task.

Cancellation model requires some flag which is accessible by the caller and from inside the operation. Caller sets the flag when wants operation to be cancelled. Operation regularly checks the flag or is subscribed to the event "flag has been set" and if flag has been set, operation will stop doing the work. This model is in .NET abstracted with two types: CancellationTokenSource class and CancellationToken structure. Caller uses CancellationTokenSource to initiate cancellation and operation uses CancellationToken to check whether it has been cancelled.

When designing an API, if provision for operation cancellation is required, we have to add a CancellationToken to a list of method's arguments. API caller invokes CancellationTokenSource.Cancel method which sets CancellationToken.IsCancellationRequested to true. Target method checks this property and can either silently return or throw OperationCanceledException. The latter approach is better as only in this case returning task will come to Canceled state.

The following code depicts the whole process of task cancellation:

CancellationToken.ThrowIfCancellationRequested() method simply checks CancellationToken.IsCancellationRequested and if it's true, it throws OperationCanceledException. The output of the code above is:


.........System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowIfCancellationRequested()
   at MyService.d__0.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


The following code shows that the await-ed task does not get aware that the task it holds got canceled if method silently returns on cancellation:

Output:

..........Task.IsCanceled: False
Task.IsFaulted: False
Task.Exception: null


Further reading:
Andrew Arnott - Recommended patterns for CancellationToken
MSDN: Task Cancellation