为什么手动引发的瞬态错误异常被处理为AggregateException?

时间:2014-10-03 20:30:28

标签: c# exception-handling error-handling azureservicebus

当我尝试手动引发瞬态异常时,它总是被处理为AggregateException。由于它被处理为AggregateException,因此在我的重试策略中不会将其作为暂时性错误处理,也不会针对预定义的重试计数进行重试。

显示瞬态错误here。 因此,我尝试了CommunicationExceptionServerErrorException,但它是作为AggregateException处理的。

当我查找AggregateException时,它会显示“表示在应用程序执行期间发生的一个或多个错误”。是的,它非常有用!!!

以下是我案例的示例代码:

我有一个使用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();
}

假设我将此功能称为:

TestManually();

我很困惑为什么手动抛出的异常(定义为瞬态错误)被处理为AggregateException?那里我想念的是什么?

感谢。

3 个答案:

答案 0 :(得分:3)

异步代码中的异常是一个棘手的主题,原因有两个。

  1. 处理异常的方式(例如catch块)并不总是直观的,可能看起来不一致。
  2. 记录异步方法引发的异常行为的方式并不总是很明显。
  3. 我将在下面列出这些项目。

    重要说明:此答案使用术语异步方法来引用返回类型为TaskTask<T>的任何方法。内置支持异步编程的语言有自己的相关术语,这些术语的含义可能不同。

    异步方法引发的异常

    异步方法能够在创建Task之前或在任务本身的异步执行期间抛出异常。虽然项目在异步代码的异常记录方式上并不总是一致的,但我希望在我的项目中包含以下注释,以便为用户提供清晰的信息。

    注意:仅假设对于明确说明它的库,以下引用为true。该声明专门用于解决上述第二个问题领域。

      

    异步方法的文档没有区分这两种情况,允许以任何一种方式抛出任何指定的异常。

    Task创建

    之前的例外情况

    在创建表示异步操作的Task对象之前抛出的异常必须由调用代码直接捕获。例如,如果代码以这种方式抛出ArgumentNullException,则调用代码需要包含ArgumentNullExceptionArgumentException的异常处理程序来处理异常。

    抛出直接异常的示例代码:

    public Task SomeOperationAsync()
    {
        throw new ArgumentException("Directly thrown.");
    }
    

    处理直接抛出异常的示例代码:

    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).
    }
    

    任务执行期间的异常

    异步执行任务期间抛出的异常包含在AggregateException对象中,并由Exception属性返回。以这种方式抛出的异常必须由检查Exception属性的任务继续处理,或者通过调用Wait或检查包含处理程序的异常处理块中的Result属性来处理。 AggregateException

    在我创建的库中,我为用户提供了额外的保证,内容如下:

    注意:仅假设对于明确说明它的库,以下引用为真。

      

    此库还可确保异步操作抛出的异常不会包含在AggregateException的多个层中。换句话说,在异步执行任务期间抛出的ArgumentException将导致Exception属性返回AggregateException,并且该异常将不包含AggregateException的任何嵌套实例在InnerExceptions集合中。在大多数情况下,AggregateException只包含一个内部异常,即原始ArgumentException。此保证简化了API的使用,支持异步 / 等待的语言,因为这些运算符会自动解包第一层AggregateException

    在任务执行期间抛出异常的示例方法:

    public Task SomeOperationAsync()
    {
        return Task.StartNew(
            () =>
            {
                throw new ArgumentException("Directly thrown.");
            });
    }
    
    public async Task SomeOtherOperationAsync()
    {
        throw new ArgumentException("async functions never throw exceptions directly.");
    }
    

    在任务执行期间处理异常的示例代码:

    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).
    }
    

    一致的异常处理

    在异步调用期间实现异常专门处理的应用程序有多个选项可用于一致处理。最简单的解决方案(如果可用)涉及使用异步 / 等待。这些运算符会自动展开InnerExceptions AggregateException集合中的第一个异常实例,从而导致调用代码时出现的行为就好像被调用的方法直接抛出异常一样。第二种方法涉及将原始调用视为另一个任务的延续,确保所有异常都作为AggregateException呈现给异常处理代码。以下代码显示了此策略在现有异步调用中的应用。请注意,CompletedTask类和Then()扩展方法是单独的Rackspace Threading Library(开源,Apache 2.0)的一部分。

    // original asynchronous method invocation
    Task task1 = SomeOperationAsync();
    
    // method invocation treated as a continuation
    Task task2 = CompletedTask.Default.Then(_ => SomeOperationAsync());
    

    使用持续策略进行一致性错误处理的代码可能会受益于Catch()方法的使用,这些方法也是Rackspace Threading Library的一部分。此扩展方法的行为类似于 await ,在调用处理异常的continuation函数之前,会自动展开InnerExceptions AggregateException集合中的第一个异常实例。 / p>

答案 1 :(得分:1)

截至Transient Fault Handling v6.0.1304.0,以下代码根据配置的检测策略成功重试:

策略:

public class SimpleHandlerStartegy : ITransientErrorDetectionStrategy
    {
        public bool IsTransient(Exception ex)
        {
            if (ex is WebException)
            {
                return true;
            }

            return false;
        }
    }

引发WebException的代码:

async Task<int> SomeAsyncWork()
        {
            await Task.Delay(1000);
            throw new WebException("This is fake");
            return 1; // Unreachable!!
        }

客户代码:

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());              

答案 2 :(得分:0)

据我所知,异步代码块中引发的异常会在聚合异常中传递回主线程。我想这是因为引发异常并不一定会导致执行返回到主线程,因此我们可能会返回多个异常。