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

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

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

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

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

我有一个使用ServiceBusTransientErrorDetectionStrategy的重试策略

 public void TestManually() { var retryPolicy = new RetryPolicy(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(); 

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

谢谢。

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

  1. 处理exception的方式(例如通过catch块)并不总是直观的,并且可能看起来不一致。
  2. 记录异步方法抛出的exception行为的方式并不总是显而易见的。

我将在下面解决这些问题。

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

异步方法引发的exception

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

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

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

创建Task之前的例外情况

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

抛出直接exception的示例代码:

 public Task SomeOperationAsync() { throw new ArgumentException("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). } 

任务执行期间的例外情况

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

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

注意:仅假设以下引用适用于明确声明它的库。

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

在任务执行期间每个抛出exception的示例方法:

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

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

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

一致的exception处理

在异步调用期间实现exception专门处理的应用程序具有多个可用于一致处理的选项。 最简单的解决方案(如果可用)涉及使用async / await 。 这些运算符会自动解包AggregateExceptionInnerExceptions集合中的第一个exception实例,从而导致调用代码时出现的行为就好像被调用的方法直接抛出exception一样。 第二种方法涉及将原始调用视为另一个任务的延续,确保所有exception都作为exception处理代码的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()); 

使用延迟策略进行一致error handling的代码可能会受益于Catch()方法的使用,这些方法也是Rackspace Threading Library的一部分。 此扩展方法的行为类似于await ,在调用处理exception的continuation函数之前,自动解包AggregateExceptionInnerExceptions集合中的第一个exception实例。

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

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