I have an IObservable set up, and I'm trying to trigger it to transition from "cold" to "hot". However, by pure dumb luck, I found that somewhere in the IObservable, an unhandled exception is throw.
I immediately freaked out because Reactive Extensions didn't let me know about this unhandled exception.
I guess the stream just terminated, instead. Now I'm worried that Rx Extensions is going to be swallowing other exceptions.
This is the code I'm currently using to trigger the IObservable to be "hot". I would expect that any unhandled exceptions that occur inside the IObservable to bubble up and be thrown here. But they are not.
var observable = Observable.Create<>(async a =>
{
...
a.OnNext();
...
a.OnCompleted();
});
observable = observable.Do(onNext: ...,
onCompleted: async () =>
{
// This throws the unhandled exception
await MethodThatThrowsExceptionAsync();
});
// I would expect any exceptions inside the IObservable to bubble up and be rethrown here.
await observable.LastOrDefaultAsync();
Am I doing something wrong? Is this expected behavior? This seems extremely error-prone if it is.
Do is for expressing side-effects on the notification, and not to actually modify the notifications themselves. Throwing errors in Do is generally not advised.
If we break this down, you want to run a task when the observable is completed, in which you may throw an exception, which bubbles down.
There's an operator which can shunt another observable once it's completed - Concat.
var observable = Observable.Create<int>(async a =>
{
await Task.Delay(1000);
a.OnNext(0);
a.OnCompleted();
});
observable = observable.Concat(Observable.FromAsync(async () =>
{
await Task.Delay(1000); //simulate work
// This throws the unhandled exception
throw new Exception("I'm from async");
return 1; //type inference
}));
// This now throws
await observable.LastOrDefaultAsync();
P.S.
Be careful when using async/await in place of Action<>.
They fail silently.
Async void methods considered harmful.
Related
I am trying to understand exception handling in TPL.
The following code seems to swallow exceptions:
var processor = new ActionBlock<int>((id) => SomeAction(id), new ExecutionDataflowBlockOptions { ... });
async Task SomeAction(int merchantID)
{
//Exception producing code
...
}
And listening to TaskScheduler.UnobservedTaskException events does not receive anything either.
So, does this mean the action block does a try-catch in itself when running the actions?
Is there any official documentation of this somewhere?
Update
The exception handling behavior of DataFlow blocks is explained in Exception Handling in TPL DataFlow Networks
**Original
This code doesn't swallow exceptions. If you await the block to complete with await processor.Completion you'll get the exception. If you use a loop to pump messages to the block before calling Complete() you need a way to stop the loop too. One way to do it is to use a CancellationTokenSource and signal it in case of exception:
void SomeAction(int i,CancellationTokenSource cts)
{
try
{
...
}
catch(Exception exc)
{
//Log the error then
cts.Cancel();
//Optionally throw
}
}
The posting code doesn't have to change all that much, it only needs to check whether
var cts=new CancellationTokenSource();
var token=cts.Token;
var dopOptions=new new ExecutionDataflowBlockOptions {
MaxDegreeOfParallelism=10,
CancellationToken=token
};
var block= new ActioBlock<int>(i=>SomeAction(i,cts),dopOptions);
while(!token.IsCancellationRequested && someCondition)
{
block.Post(...);
}
block.Complete();
await block.Completion;
When the action throws, the token is signaled and the block ends. If the exception is rethrown by the action, it will be rethrown by await block.Completion as well.
If that seems convoluted, it's because that's somewhat of an edge case for blocks. DataFlow is used to create pipelines or networks of blocks.
The general case
The name Dataflow is significant.
Instead of building a program by using methods that call each other, you have processing blocks that pass messages to each other. There's no parent method to receive results and exceptions. The pipeline of blocks remains active to receive and process messages indefinitely, until some external controller tells it to stop, eg by calling Complete on the head block, or signaling the CancellationToken passed to each block.
A block shouldn't allow unhandled exceptions to occur, even if it's a standalone ActionBlock. As you saw, unless you've already called Complete() and await Completion, you won't get the exception.
When an unhandled exception occurs inside a block, the block enters the faulted state. That state propagates to all downstream blocks that are linked with the PropagateCompletion option. Upstream blocks aren't affected, which means they may keep working, storing messages in their output buffers until the process runs out of memory, or deadlocks because it receives no responses from the blocks.
Proper Failure Handling
The block should catch exceptions and decide what to do, based on the application's logic:
Log it and keep processing. That's not that different from how web application's work - an exception during a request doesn't bring down the server.
Send an error message to another block, explicitly. This works but this type of hard-coding isn't very dataflow-ish.
Use message types with some kind of error indicator. Perhaps a Success flag, perhaps an Envelope<TMessage> object that contains either a message or an error.
Gracefully cancel the entire pipeline, by signaling all blocks to cancel by signaling the CancellationTokenSource used to produce the CancellationTokens used by all blocks. That's the equivalent of throw in a common program.
#3 is the most versatile option. Downstream blocks can inspect the Envelope and ignore or propagate failed messages without processing. Essentially, failed messages bypass downstream blocks.
Another option is to use the predicate parameter in LinkTo and send failed messages to a logger block and successful messages to the next downstream block. In complex scenarios, this could be used to eg retry some operations and send the result downstream.
These concepts, and the image, come from Scott Wlaschin's Railway Oriented Programming
The TaskScheduler.UnobservedTaskException event is not a reliable/deterministic way to handle exceptions of faulted tasks, because it's delayed until the faulted task is cleaned up by the garbage collector. And this may happen long after the error occurred.
The only type of exception that is swallowed by the dataflow blocks is the OperationCanceledException (AFAIK for non-documented reasons). All other exceptions result to the block transitioning to a faulted state. A faulted block has its Completion property (which is a Task) faulted as well (processor.Completion.IsFaulted == true). You can attach a continuation to the Completion property, to receive a notification when a block fails. For example you could ensure that an exception will not pass unnoticed, by simply crashing the process:
processor.Completion.ContinueWith(t =>
{
ThreadPool.QueueUserWorkItem(_ => throw t.Exception);
}, default, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
This works because throwing an unhandled exception on the ThreadPool causes the application to terminate (after raising the AppDomain.CurrentDomain.UnhandledException event).
If your application has a GUI (WinForms/WPF etc), then you could throw the exception on the UI thread, that allows more graceful error handling:
var uiContext = SynchronizationContext.Current;
processor.Completion.ContinueWith(t =>
{
uiContext.Post(_ => throw t.Exception, null);
}, default, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
This will raise the Application.ThreadException event in WinForms.
I have a console application using Rx.NET.
I need to block until an IConnectableObservable has completed, for which I am using IConnectableObservable.Wait().
When an unhandled exception is thrown, it is swallowed and the application hangs. I want the application to crash, and the stack track to be printed to the console.
I do not want to add an OnError handler to my IConnectableObserver because this loses the original stack trace.
I have tried using the .Wait() method on the unpublished observable, but this re-subscribes which causes undesirable behaviour.
I have tried using .GetAwaiter().GetResult() instead, but this has the same problem.
var connectable = myObservable.Publish();
connectable.Subscribe(myObserver1);
connectable.Subscribe(myObserver2);
connectable.Connect();
connectcable.Wait();
How can I wait for an IConnectableObservable to complete while retaining typical unhandled exception behaviour?
There's some misdirection in the chain of events here.
The error isn't being swallowed - far from it, it's being re-thrown.
The usual suspects are some weird concurrency and scheduling issues, but nobody suspects the Subscribe method.
When you call Subscribe with something other than your own IObserver<T>, you're creating an AnonymousObserver with these default actions.
new AnonymousObserver<T>(Ignore, Throw, Nop)
which is effectively
new AnonymousObserver<T>(_ => {}, exn => throw exn, () => {})
The default error handler will throw the error on whatever context you're observing on. Yikes. Sometimes it might be the AppDomain timer, or on a pooled thread, and since it can't be handled, your application goes down.
So if we change the sample to provide in a dummy handler,
var myObservable = Observable.Interval(TimeSpan.FromMilliseconds(100)).Take(4).Concat(Observable.Throw(new Exception(), 1L));
var connectable = myObservable.Publish();
connectable.Subscribe(Console.WriteLine, exn => Console.WriteLine("Handled"));
connectable.Subscribe(Console.WriteLine, exn => Console.WriteLine("Handled"));
connectable.Connect();
try
{
connectable.Wait();
}
catch (Exception)
{
Console.WriteLine("An error, but I'm safe");
}
You can handle the error in the Wait like you'd expect to.
Goal:
I am confused by the behavior I am seeing with exceptions in my .Net Core library. The goal of this question is to understand why it is doing what I am seeing.
Executive Summary
I thought that when an async method is called, the code in it is executed synchronously until it hits the first await. If that is the case, then, if an exception is thrown during that "synchronous code", why is it not propagated up to the calling method? (As a normal synchronous method would do.)
Example Code:
Given the following code in a .Net Core Console Application:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
try
{
NonAwaitedMethod();
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
Console.ReadKey();
}
public static async Task NonAwaitedMethod()
{
Task startupDone = new Task(() => { });
var runTask = DoStuff(() =>
{
startupDone.Start();
});
var didStartup = startupDone.Wait(1000);
if (!didStartup)
{
throw new ApplicationException("Fail One");
}
await runTask;
}
public static async Task DoStuff(Action action)
{
// Simulate starting up blocking
var blocking = 100000;
await Task.Delay(500 + blocking);
action();
// Do the rest of the stuff...
await Task.Delay(3000);
}
}
Scenarios:
When run as is, this code will throw an exception, but, unless you have a break point on it, you will not know it. The Visual Studio Debugger nor the Console will give any indication that there was an issue (aside from a one line note in the Output screen).
Swap the return type of NonAwaitedMethod from Task to void. This will cause the Visual Studio Debugger to now break on the exception. It will also be printed out in the console. But notably, the exception is NOT caught in the catch statement found in Main.
Leave the return type of NonAwaitedMethod as void, but take off the async. Also change the last line from await runTask; to runTask.Wait(); (This essentially removes any async stuff.) When run, the exception is caught in the catch statement in the Main method.
So, to summarize:
| Scenario | Caught By Debugger | Caught by Catch |
|------------|--------------------|-----------------|
| async Task | No | No |
| async void | Yes | No |
| void | N/A | Yes |
Questions:
I thought that because the exception was thrown before an await was done, that it would execute synchronously up to, and through the throwing of the exception.
Hence my question of: Why does neither scenario 1 or 2 get caught by the catch statement?
Also, why does swapping from Task to void return type cause the exception to get caught by the Debugger? (Even though I am not using that return type.)
exception was thrown before an await was done, that it would execute synchronously
Thought this is fairly true, but it doesn't mean you could catch the exception.
Because your code has async keyword, which turns the method into an async state machine i.e. encapsulated / wrapped by a special type. Any exception thrown from async state machine will get caught and re-thrown when the task is awaited (except for those async void ones) or they go unobserved, which can be caught in TaskScheduler.UnobservedTaskException event.
If you remove async keyword from the NonAwaitedMethod method, you can catch the exception.
A good way to observe this behavior is using this:
try
{
NonAwaitedMethod();
// You will still see this message in your console despite exception
// being thrown from the above method synchronously, because the method
// has been encapsulated into an async state machine by compiler.
Console.WriteLine("Method Called");
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
So your code is compiled similarly to this:
try
{
var stateMachine = new AsyncStateMachine(() =>
{
try
{
NonAwaitedMethod();
}
catch (Exception ex)
{
stateMachine.Exception = ex;
}
});
// This does not throw exception
stateMachine.Run();
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
why does swapping from Task to void return type cause the exception to get caught
If the method returns a Task, the exception is caught by the task.
If the method is void, then the exception gets re-thrown from an arbitrary thread pool thread. Any unhandled exception thrown from thread pool thread will cause the app to crash, so chances are the debugger (or maybe the JIT debugger) is watching this sort of exceptions.
If you want to fire and forget but properly handle the exception, you could use ContinueWith to create a continuation for the task:
NonAwaitedMethod()
.ContinueWith(task => task.Exception, TaskContinuationOptions.OnlyOnFaulted);
Note you have to visit task.Exception property to make the exception observed, otherwise, task scheduler still will receive UnobservedTaskException event.
Or if the exception needs to be caught and processed in Main, the correct way to do that is using async Main methods.
if an exception is thrown during that "synchronous code", why is it not propagated up to the calling method? (As a normal synchronous method would do.)
Good question. And in fact, the early preview versions of async/await did have that behavior. But the language team decided that behavior was just too confusing.
It's easy enough to understand when you have code like this:
if (test)
throw new Exception();
await Task.Delay(TaskSpan.FromSeconds(5));
But what about code like this:
await Task.Delay(1);
if (test)
throw new Exception();
await Task.Delay(TaskSpan.FromSeconds(5));
Remember that await acts synchronously if its awaitable is already completed. So has 1 millisecond gone by by the time the task returned from Task.Delay is awaited? Or for a more realistic example, what happens when HttpClient returns a locally cached response (synchronously)? More generally, the direct throwing of exceptions during the synchronous part of the method tends to result in code that changes its semantics based on race conditions.
So, the decision was made to unilaterally change the way all async methods work so that all exceptions thrown are placed on the returned task. As a nice side effect, this brings their semantics in line with enumerator blocks; if you have a method that uses yield return, any exceptions will not be seen until the enumerator is realized, not when the method is called.
Regarding your scenarios:
Yes, the exception is ignored. Because the code in Main is doing "fire and forget" by ignoring the task. And "fire and forget" means "I don't care about exceptions". If you do care about exceptions, then don't use "fire and forget"; instead, await the task at some point. The task is how async methods report their completion to their callers, and doing an await is how calling code retrieves the results of the task (and observe exceptions).
Yes, async void is an odd quirk (and should be avoided in general). It was put in the language to support asynchronous event handlers, so it has semantics that are similar to event handlers. Specifically, any exceptions that escape the async void method are raised on the top-level context that was current at the beginning of the method. This is how exceptions also work for UI event handlers. In the case of a console application, exceptions are raised on a thread pool thread. Normal async methods return a "handle" that represents the asynchronous operation and can hold exceptions. Exceptions from async void methods cannot be caught, since there is no "handle" for those methods.
Well, of course. In this case the method is synchronous, and exceptions travel up the stack just like normal.
On a side note, never, ever use the Task constructor. If you want to run code on the thread pool, use Task.Run. If you want to have an asynchronous delegate type, use Func<Task>.
The async keyword indicates that the compiler should transform the method to an async state machine, which is not configurable regarding the handling of the exceptions. If you want the sync-part-exceptions of the NonAwaitedMethod method to be thrown immediately, there is no other option than removing the async keyword from the method. You can have the best of both worlds by moving the async part into an async local function:
public static Task NonAwaitedMethod()
{
Task startupDone = new Task(() => { });
var runTask = DoStuff(() =>
{
startupDone.Start();
});
var didStartup = startupDone.Wait(1000);
if (!didStartup)
{
throw new ApplicationException("Fail One");
}
return ImlpAsync(); async Task ImlpAsync()
{
await runTask;
};
}
Instead of using a named function, you could also use an anonymous one:
return ((Func<Task>)(async () =>
{
await runTask;
}))();
When I write Task-based code, some of my ContinueWith clauses intentionally throw Exceptions (which I catch and handle appropriately), and some of them accidentally throw Exceptions (because of bugs). How can I avoid breaking on the first kind while still breaking on the second kind?
In the code below, I expect the debugger to break on the Unintentional Exception, and NOT on the Intentional Exception (because it is handled later). If I disable "Just My Code" according to this question, then the Intentional Exception doesn't break the debugger (correct), but the Unintentional Exception doesn't break the debugger either (incorrect). If I enable "Just My Code", then the Unintentional Exception does break the debugger (correct), but so does the Intentional Exception (incorrect).
Is there any setting to make Exceptions in ContinueWith clauses work like a normal developer would presumably expect them to?
class Program
{
static void Main(string[] args)
{
//Intentional Exception
Task.Factory
.StartNew(() => Console.WriteLine("Basic action"))
.ContinueWith((t1) => { throw new Exception("Intentional Exception"); })
.ContinueWith((t2) => Console.WriteLine("Caught '" + t2.Exception.InnerException.Message + "'"));
//Unintentional Exception
Task.Factory
.StartNew(() => Console.WriteLine("Basic action"))
.ContinueWith((t3) => { throw new Exception("Unintentional Exception (bug)"); });
Console.ReadLine();
}
}
There are two possible approaches to address this problem. Neither are very satisfying as they essentially rely on the developer detecting 100% their own bugs which is really something the debugger should be doing in a development setting. For either approach, the developer should first turn off "Just My Code" as this is the way to prevent the caught/observed Intentional Exception from causing the debugger to break.
First, as #Peter Deniho mentions in his answer and its comments, the developer can listen for the TaskScheduler.UnobservedTaskException event. The code for this is simple:
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
throw new Exception("An Exception occurred in a Task but was not observed", e.Exception.InnerException);
};
The difficulty with this approach is that this event only fires when the faulted Task's finalizer is called. This happens during garbage collection, but garbage collection may not happen until long after the Exception occurs. To see that this is a serious issue, use the approach mentioned later in this paragraph, but comment out GC.Collect(). In that scenario, the Heartbeats will go on for a very long time before the unobserved Exception is thrown. To deal with this issue, garbage collection must be forced as frequently as you want to make sure your unobserved/Unintentional Exception is reported to the developer. This can be accomplished by defining the following method and calling it at the beginning of the original static void Main:
private static async void WatchForUnobservedExceptions()
{
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
throw new Exception("An Exception occurred in a Task but was not observed", e.Exception.InnerException);
};
//This polling loop is necessary to ensure the faulted Task's finalizer is called (which causes UnobservedTaskException to fire) in a timely fashion
while (true)
{
Console.WriteLine("Heartbeat");
GC.Collect();
await Task.Delay(5000);
}
}
Of course, this has serious performance implications and the fact that this is necessary is a pretty good indicator of fundamentally broken code.
The above approach could be characterized as periodically polling to automatically check Tasks' Exceptions that might have been missed. A second approach is to explicitly check each Task's Exception. That means the developer must identify any "background" Tasks whose Exceptions are not ever observed (via await, Wait(), Result, or Exception) and do something with any Exceptions that are unobserved. This is my preferred extension method:
static class Extensions
{
public static void CrashOnException(this Task task)
{
task.ContinueWith((t) =>
{
Console.WriteLine(t.Exception.InnerException);
Debugger.Break();
Console.WriteLine("The process encountered an unhandled Exception during Task execution. See above trace for details.");
Environment.Exit(-1);
}, TaskContinuationOptions.OnlyOnFaulted);
}
}
Once defined, this method can simply be appended to any Task that might otherwise have unobserved Exceptions if it had bugs:
Task.Factory
.StartNew(() => Console.WriteLine("Basic action"))
.ContinueWith((t3) => { throw new Exception("Unintentional Exception (bug)"); })
.CrashOnException();
The primary drawback of this approach is that each approriate task must be identified (most Tasks will not need or want this modification since most Tasks are used at some future point in the program) and then suffixed by the developer. If the developer forgets to suffix a Task with an unobserved Exception, it will be silently swallowed just like the behavior of the Unintentional Exception in the original question. This means that this approach is less effective at catching bugs due to carelessness even though doing that is what this question is about. In exchange for the reduced likelihood of catching bugs, you do not take the performance hit of the first approach.
I'm pretty sure you can't do what you want here, not exactly.
Note: your code example is a bit misleading. There's no reason the text This line should never print should in fact never print. You don't wait on the tasks in any way, so the output happens before the tasks even get far enough to throw exceptions.
Here's a code example that illustrates what you're asking about, but which IMHO is a bit more robust and which is a better starting point to demonstrate the different behaviors involved:
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
Console.WriteLine();
Console.WriteLine("Unobserved exception: " + e.Exception);
Console.WriteLine();
};
//Intentional Exception
try
{
Console.WriteLine("Test 1:");
Task.Factory
.StartNew(() => Console.WriteLine("Basic action"))
.ContinueWith((t1) => { throw new Exception("Intentional Exception"); })
.ContinueWith(t2 => Console.WriteLine("Caught '" +
t2.Exception.InnerException.Message + "'"))
.Wait();
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e);
}
Console.WriteLine();
//Unintentional Exception
try
{
Console.WriteLine("Test 2:");
Task.Factory
.StartNew(() => Console.WriteLine("Basic action"))
.ContinueWith(
t3 => { throw new Exception("Unintentional Exception (bug)"); })
.Wait();
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e);
}
Console.WriteLine();
Console.WriteLine("Done running tasks");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Done with GC.Collect() and finalizers");
Console.ReadLine();
The fundamental issue is that even in your first Task example, you're not really handling the exception. The Task class already did that. All you're doing is, by adding the continuation, is adding a new Task that itself won't throw an exception, because of course it doesn't throw an exception.
If you were instead to wait on the original continuation (i.e. the one that throws the exception), you'd find the exception wasn't in fact handled. That task will still throw an exception:
Task task1 = Task.Factory
.StartNew(() => Console.WriteLine("Basic action"))
.ContinueWith(t1 => { throw new Exception("Intentional Exception"); });
Task task2 = task1
.ContinueWith(t2 => Console.WriteLine("Caught '" +
t2.Exception.InnerException.Message + "'"));
task2.Wait();
task1.Wait(); // exception thrown here
I.e. in the above, task2.Wait() is fine, but task1.Wait() still throws the exception.
The Task class does have the idea of whether an exception has been "observed" or not. So even if you don't wait on the first task, as long as you retrieve the Exception property in the continuation (as your example) does, the Task class is happy.
But note that if you change the continuation so that it ignores the Exception property value and you don't wait on the exception-throwing task, when the finalizers run in my version of your example, you will find that there is an unobserved task exception reported.
Task task1 = Task.Factory
.StartNew(() => Console.WriteLine("Basic action"))
.ContinueWith(t1 => { throw new Exception("Intentional Exception"); });
Task task2 = task1
.ContinueWith(t2 => Console.WriteLine("Caught exception"));
task2.Wait();
task1 = null; // ensure the object is in fact collected
So what does all this actually mean in the context of your question? Well, to me it means that the debugger would have to have special logic built into it to recognize these scenarios and understand that even though the exception isn't being handled, it is being "observed" per the rules of the Task class.
I don't know how much work that would be, but it sounds like a lot of work to me. Furthermore, it would be an example of the debugger including detailed information about a specific class in .NET, something that I think would generally be desirable to avoid. In any case, I doubt that work has been done. As far as the debugger is concerned, in both cases you've got an exception and you didn't handle it. It has no way to tell the difference between the two.
Now, all that said…
It seems to me that in your real code you probably aren't actually throwing the plain vanilla Exception type. So you do have the option of using the "Debug/Exceptions…" dialog:
…to enable or disable breaking on specific exceptions. For those that you know, in spite of them actually not being handled, are effectively observed and thus you don't need to break in the debugger when they happen, you can disable breaking on those exceptions in the debugger. The debugger will still break on other exceptions, but will ignore the ones you've disabled.
Of course, if there's a possibility you will accidentally fail to catch/handle/observe one of the exceptions that you otherwise are normally correctly dealing with, then this isn't an option. You'll just have to live with the debugger break and continue.
Not ideal, but frankly should not be a huge problem either; after all, exceptions are by definition for exceptional situations. They shouldn't be used for flow control or inter-thread communication, and so should occur only very rarely. Most tasks should be catching their own exceptions and handling them gracefully, so you should not have to be hitting F5 very often.
When I try to raise transient exception manually, it is always handled as AggregateException. Since it is handled as AggregateException , it is not handled as transient error in my retry policy and not retried for the predefined retry count.
Transient errors are shown here .
Therefore I have tried CommunicationException and ServerErrorException but it is handled as an AggregateException.
When I look for AggregateException, it says "Represents one or more errors that occur during application execution." Yeah, it is so helpful!!!
Here is the example code of my case:
I have a retry policy which uses ServiceBusTransientErrorDetectionStrategy
public void TestManually()
{
var retryPolicy = new RetryPolicy<ServiceBusTransientErrorDetectionStrategy>(RetryStrategy.DefaultFixed);
retryPolicy.Retrying += (obj, eventArgs) =>
{
Trace.TraceError("Hey!! I'm Retrying, CurrentRetryCount = {0} , Exception = {1}", eventArgs.CurrentRetryCount, eventArgs.LastException.Message);
};
retryPolicy.ExecuteAsync(() =>
MyTestFunction().ContinueWith(t =>
{
if (t.Exception != null)
{
// A non-transient exception occurred or retry limit has been reached
Trace.TraceError("This was not a transient exxception... It was: " + t.Exception.GetType().ToString());
}
}));
}
public Task MyTestFunction()
{
Task task = Task.Factory.StartNew(() => RaiseTransientErrorManually());
return task;
}
public void RaiseTransientErrorManually()
{
//throw new CommunicationException();
throw new ServerErrorException();
}
Let's say I call my function like this:
TestManually();
I'm very confused why the manually thrown exception (which is defined as Transient Error) is handled as AggregateException ? What I'm missing there?
Thanks.
Exceptions within asynchronous code are a tricky subject for two reasons.
The manner in which exceptions are handled (e.g. by catch blocks) is not always intuitive, and may seem inconsistent.
The manner in which libraries document the behavior for exceptions thrown by asynchronous methods is not always obvious.
I'll address each of these items below.
Important Note: This answer uses the term asynchronous method to refer to any method with a return type of Task or Task<T>. Languages with built-in support for asynchronous programming have their own related terminology which may differ in meaning.
Exceptions Thrown by Asynchronous Methods
Asynchronous methods are capable of throwing exceptions before creating a Task or during the asynchronous execution of the task itself. While projects are not always consistent in the way exceptions are documented for asynchronous code, I like to include the following note with my projects to make things clear for my users.
Note: only assume the following quote is true for a library which explicitly states it. The statement is specifically meant to address the second problem area described above.
The documentation for asynchronous methods does not distinguish between these two cases, allowing for any of the specified exceptions to be thrown in either manner.
Exceptions Prior to Task Creation
Exceptions thrown prior to the creation of the Task object representing the asynchronous operation must be caught directly by the calling code. For example, if the code throws an ArgumentNullException in this manner, the calling code would need to contain an exception handler for ArgumentNullException or ArgumentException to handle the exception.
Example code which throws a direct exception:
public Task SomeOperationAsync()
{
throw new ArgumentException("Directly thrown.");
}
Example code which handles a directly-thrown exception:
try
{
Task myTask = SomeOperationAsync();
}
catch (ArgumentException ex)
{
// ex was thrown directly by SomeOperationAsync. This cannot occur if
// SomeOperationAsync is an async function (§10.15 - C# Language Specification
// Version 5.0).
}
Exceptions During Task Execution
Exceptions thrown during the asynchronous execution of the task are wrapped in an AggregateException object and returned by the Exception property. Exceptions thrown in this manner must be handled either by a task continuation that checks the Exception property, or by calling Wait or checking the Result property within an exception handling block that includes a handler for AggregateException.
In libraries that I create, I provide an additional guarantee for users which reads as follows:
Note: only assume the following quote is true for a library which explicitly states it.
This library additionally ensures that exceptions thrown by asynchronous operations are not wrapped in multiple layers of AggregateException. In other words, an ArgumentException thrown during the asynchronous execution of a task will result in the Exception property returning an AggregateException, and that exception will not contain any nested instances of AggregateException in the InnerExceptions collection. In most cases, the AggregateException wraps exactly one inner exception, which is the original ArgumentException. This guarantee simplifies the use of the API is languages that support async/await, since those operators automatically unwrap the first layer of AggregateException.
Example methods which each throw an exception during task execution:
public Task SomeOperationAsync()
{
return Task.StartNew(
() =>
{
throw new ArgumentException("Directly thrown.");
});
}
public async Task SomeOtherOperationAsync()
{
throw new ArgumentException("async functions never throw exceptions directly.");
}
Example code which handles an exception during task execution:
try
{
Task myTask = SomeOperationAsync();
myTask.Wait();
}
catch (AggregateException wrapperEx)
{
ArgumentException ex = wrapperEx.InnerException as ArgumentException;
if (ex == null)
throw;
// ex was thrown during the asynchronous portion of SomeOperationAsync. This is
// always the case if SomeOperationAsync is an async function (§10.15 - C#
// Language Specification Version 5.0).
}
Consistent Exception Handling
Applications implementing specialized handling for exception which occur during asynchronous calls have multiple options available for consistent handling. The simplest solution, when available, involves using async/await. These operators automatically unwrap the first exception instance in the InnerExceptions collection of an AggregateException, resulting in behavior that appears to calling code as though the exception was directly thrown by the invoked method. The second method involves treating the original call as a continuation of another task, ensuring that all exceptions are presented as an AggregateException to the exception handling code. The following code shows the application of this strategy to an existing asynchronous call. Note that the CompletedTask class and Then() extension method are part of the separate Rackspace Threading Library (open-source, Apache 2.0).
// original asynchronous method invocation
Task task1 = SomeOperationAsync();
// method invocation treated as a continuation
Task task2 = CompletedTask.Default.Then(_ => SomeOperationAsync());
Code using the continuation strategy for consistent error handling may benefit from the use of the Catch() methods, which are also part of the Rackspace Threading Library. This extension method behaves in a manner similar to await, automatically unwrapping the first exception instance in the InnerExceptions collection of an AggregateException before invoking the continuation function which handles the exception.
As of Transient Fault Handling v6.0.1304.0 , the following code successfully retries as per the configured detection strategy:
Strategy:
public class SimpleHandlerStartegy : ITransientErrorDetectionStrategy
{
public bool IsTransient(Exception ex)
{
if (ex is WebException)
{
return true;
}
return false;
}
}
Code That Throws WebException:
async Task<int> SomeAsyncWork()
{
await Task.Delay(1000);
throw new WebException("This is fake");
return 1; // Unreachable!!
}
Client Code:
var retryStrategy = new Incremental(3, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
var retryPolicy = new RetryPolicy<SimpleHandlerStartegy>(retryStrategy);
retryPolicy.Retrying += (sender, retryArgs) =>
{
Console.WriteLine("Retrying {0}, Delay{1}, Last Exception: {2}", retryArgs.CurrentRetryCount, retryArgs.Delay, retryArgs.LastException);
};
// In real world, await this to get the return value
retryPolicy.ExecuteAsync(() => SomeAsyncWorkThatThrows());
As far as I understand, exceptions that are raised within an asynchronous code block are delivered back to the main thread within an aggregate exception. I suppose this is because raising an exception doesn't necessarily cause execution to be returned to the main thread and therefore we could have more than one exception returned.