为什么手动引发的瞬态错误exception被处理为AggregateException?
当我尝试手动引发瞬态exception时,它总是作为AggregateException
处理。 由于它作为AggregateException
处理,因此在我的重试策略中不会将其作为暂时性error handling,也不会针对预定义的重试计数进行重试。
此处显示瞬态错误。 因此,我尝试了CommunicationException
和ServerErrorException
但它作为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是一个棘手的主题,原因有两个。
- 处理exception的方式(例如通过
catch
块)并不总是直观的,并且可能看起来不一致。 - 库记录异步方法抛出的exception行为的方式并不总是显而易见的。
我将在下面解决这些问题。
重要说明:此答案使用术语异步方法来引用返回类型为Task
或Task
任何方法。 内置支持异步编程的语言有自己的相关术语,这些术语的含义可能不同。
异步方法引发的exception
异步方法能够在创建Task
之前或在任务本身的异步执行期间抛出exception。 虽然项目在异步代码的exception记录方式上并不总是一致的,但我希望在我的项目中包含以下注释,以便为用户提供清晰的信息。
注意:仅假设以下引用适用于明确声明它的库。 该声明专门用于解决上述第二个问题领域。
异步方法的文档不区分这两种情况,允许以任何一种方式抛出任何指定的exception。
创建Task
之前的例外情况
在创建表示异步操作的Task
对象之前抛出的exception必须由调用代码直接捕获。 例如,如果代码以这种方式抛出ArgumentNullException
,则调用代码需要包含ArgumentNullException
或ArgumentException
的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 。 这些运算符会自动解包AggregateException
的InnerExceptions
集合中的第一个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函数之前,自动解包AggregateException
的InnerExceptions
集合中的第一个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。