问题
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.
回答1:
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, anArgumentException
thrown during the asynchronous execution of a task will result in theException
property returning anAggregateException
, and that exception will not contain any nested instances ofAggregateException
in theInnerExceptions
collection. In most cases, theAggregateException
wraps exactly one inner exception, which is the originalArgumentException
. This guarantee simplifies the use of the API is languages that support async/await, since those operators automatically unwrap the first layer ofAggregateException
.
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.
回答2:
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());
回答3:
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.
来源:https://stackoverflow.com/questions/26186351/why-the-manual-raised-transient-error-exceptions-are-handled-as-an-aggregateexce