Why the manual raised Transient Error exceptions are handled as an AggregateException?

落爺英雄遲暮 提交于 2019-12-23 05:13:10

问题


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.

  1. The manner in which exceptions are handled (e.g. by catch blocks) is not always intuitive, and may seem inconsistent.
  2. 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.




回答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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!