Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper way to propagate exceptions in continuation chains?

What is the proper way to propagate exceptions in continuation chains?

t.ContinueWith(t2 => 
{
     if(t2.Exception != null)
         throw t2.Exception;

     /* Other async code. */
})
.ContinueWith(/*...*/);   

t.ContinueWith(t2 => 
{
     if(t2.IsFaulted)
         throw t2.Exception;

     /* Other async code. */
})
.ContinueWith(/*...*/);

t.ContinueWith(t2 => 
{
     if(t2.Exception != null)
         return t2;

     /* Other async code. */
})
.ContinueWith(/*...*/);   

t.ContinueWith(t2 => 
{
     if(t2.IsFaulted)
         return t2;

     /* Other async code. */
})
.ContinueWith(/*...*/);


t.ContinueWith(t2 => 
{
     t2.Wait();

     /* Other async code. */
})
.ContinueWith(/*...*/);

t.ContinueWith(t2 => 
{     
     /* Other async code. */
}, TaskContinuationOptions.NotOnFaulted) // Don't think this one works as expected
.ContinueWith(/*...*/);
like image 589
ronag Avatar asked Mar 18 '13 14:03

ronag


People also ask

How do you propagate exceptions in Python?

When an exception is raised, the exception-propagation mechanism takes control. The normal control flow of the program stops, and Python looks for a suitable exception handler. Python's try statement establishes exception handlers via its except clauses.

How do you handle exceptions in tasks?

Exceptions are propagated when you use one of the static or instance Task. Wait methods, and you handle them by enclosing the call in a try / catch statement. If a task is the parent of attached child tasks, or if you are waiting on multiple tasks, multiple exceptions could be thrown.

When to use exceptions C#?

The method to choose depends on how often you expect the event to occur. Use exception handling if the event doesn't occur very often, that is, if the event is truly exceptional and indicates an error (such as an unexpected end-of-file). When you use exception handling, less code is executed in normal conditions.

Which of the following syntax addition is used to propagate an exception from a procedure?

To propagate exceptions from procedures, the RAISINGaddition must usually be used for the definition of the interface of a procedure (except for static constructors and event handlers).


3 Answers

The approach we now use in the openstack.net SDK is the extension methods in CoreTaskExtensions.cs.

The methods come in two forms:

  • Then: The continuation returns a Task, and Unwrap() is called automatically.
  • Select: The continuation returns an object, and no call to Unwrap() occurs. This method is only for lightweight continuations since it always specifies TaskContinuationOptions.ExecuteSynchronously.

These methods have the following benefits:

  1. Avoids calling the continuation method when the antecedent is faulted or cancelled.
  2. Instead, the result of the antecedent becomes the result of the chained operation (accurately preserving exception information, without wrapping the exception in multiple layers of AggregateException).
  3. Allows callers to write continuation methods that support faulted antecedents, in which case only cancelled antecedent tasks bypass the continuation (specify supportsErrors=true for the extension methods).
  4. Continuations that return a Task are executed synchronously and Unwrap() is called for you.

The following comparison shows how we applied this change to CloudAutoScaleProvider.cs, which originally used ContinueWith and Unwrap extensively:
https://github.com/openstacknetsdk/openstack.net/compare/3ae981e9...299b9f67#diff-3

like image 103
Sam Harwell Avatar answered Oct 22 '22 01:10

Sam Harwell


The TaskContinuationOptions.OnlyOn... can be problematic because they cause the continuation to be cancelled if their condition is not met. I had some subtle problems with code I wrote before I understood this.

Chained continuations like this are actually quite hard to get right. By far the easiest fix is to use the new .NET 4.5 await functionality. This allows you almost to ignore the fact you're writing asynchronous code. You can use try/catch blocks just as you might in the synchronous equivalent. For .NET 4, this is available using the async targeting pack.

If you're on .NET 4.0, the most straightforward approach is to access Task.Result from the antecendent task in each continuation or, if it doesn't return a result, use Task.Wait() as you do in your sample code. However, you're likely to end up with a nested tree of AggregateException objects, which you'll need to unravel later on in order to get to the 'real' exception. (Again, .NET 4.5 makes this easier. While Task.Result throws AggregateException, Task.GetAwaiter().GetResult()—which is otherwise equivalent—throws the underlying exception.)

To reiterate that this is actually not a trivial problem, you might be interested in Eric Lippert's articles on exception handling in C# 5 async code here and here.

like image 29
Olly Avatar answered Oct 22 '22 00:10

Olly


If you don't want to do anything in particular in the event of an exception (i.e. logging) and just want the exception to be propagated then just don't run the continuation when exceptions are thrown (or in the event of cancellation).

task.ContinueWith(t =>
{
    //do stuff
}, TaskContinuationOptions.OnlyOnRanToCompletion);

If you explicitly want to handle the case of an exception (perhaps to do logging, change the exception thrown to be some other type of exception (possibly with additional information, or to obscure information that shouldn't be exposed)) then you can add a continuation with the OnlyOnFaulted option (possibly in addition to a normal case continuation).

like image 32
Servy Avatar answered Oct 22 '22 01:10

Servy