全局捕获在后台线程中从WCF异步调用抛出的exception

我有一个与WCF服务通信的WPF应用程序。 我正在使用以下基于async的模式从我的ViewModel调用我的WCF服务(我正在使用MVVM模式):

 public async override void MyCommandImplementation() { using (var proxy = new MyProxy()) { var something = await proxy.GetSomethingAsync(); this.MyProperty = something; } } 

当我遵循MVVM模式时,我有我的ViewModel公开的ICommand公共属性,因此关联的命令实现不会返回Task对象,因为它们就像事件处理程序一样。 所以exception处理实际上非常简单,即我能够使用以下模式捕获从我的WCF服务抛出的任何exception:

 public async override void MyCommandImplementation() { try { using (var proxy = new MyProxy()) { var something = await proxy.GetSomethingAsync(); } } catch (FaultException ex) { // Do something here } } 

到目前为止一切顺利,如果服务器抛出一个由于自定义WCF行为而自动转换为SOAP错误的exception,一切都按预期工作。

因为我有一些常见的exception可以在我的服务中的任何地方抛出(例如,每个WCF操作都可以抛出一个AuthenticationException ,它将在客户端转换为FaultExceptionexception),我决定处理一些exception我的应用程序中的一个常见位置,即通过处理Application.DispatcherUnhandledException事件。 这很好用,我可以在任何地方捕获所有FaultExceptionexception,向用户显示错误消息,并阻止应用程序退出:

 private static void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { // Exception handler that handles some common exceptions // such as FaultException if (GlobalExceptionHandler.HandleException(e.Exception)) { // Expected exception, so we're fine e.Handled = true; } else { // We're not fine. We couldn't handle the exception // so we'll exit the application // Log...etc. } } 

一切都运行良好,因为在await关键字之前/之后的async模式和同步上下文切换,在UI线程中抛出了FaultException

我的问题是,在我的UI线程之外的另一个线程中可以抛出其他exception,例如在await proxy.GetSomethingAsync();抛出EndPointNotFoundException情况下await proxy.GetSomethingAsync(); line(服务器端WCF服务关闭的情况)。

这些exception不会在Application.DispatcherUnhandledException事件处理程序中处理,因为它们不会在UI线程中抛出。 我可以在AppDomain.UnhandledException事件中处理它们,但除了执行一些日志记录并退出应用程序之外我无法做任何事情(基本上没有“e.Handled”类似的属性)。

所以我的问题是:如果在我的应用程序的一个地方发生异步WCF调用,我怎么能处理后台线程中抛出的exception?

我现在能想到的最好的东西如下:

 public class ExceptionHandler : IDisposable { public void HandleException(Exception ex) { // Do something clever here } public void Dispose() { // Do nothing here, I just want the 'using' syntactic sugar } } ... public async override void MyCommandImplementation() { using (var handler = new ExceptionHandler()) { try { using (var proxy = new MyProxy()) { var something = await proxy.GetSomethingAsync(); } } catch (FaultException ex) { // Do something here } catch (Exception ex) { // For other exceptions in any thread handler.HandleException(ex); } } } 

但这需要我重构很多代码(每次我异步调用Web服务)。

任何允许我重构大量代码的想法都会有所帮助。

通常,我不是集中/全局exception处理的忠实粉丝。 我个人的偏好是要么在任何地方处理exception,要么编写自己的代理包装器对象来处理/转换预期的错误exception。

也就是说,有一种方法可以考虑(尽管它需要修改所有命令)。

首先,将实际逻辑分解为async Task方法,如下:

 public async Task MyCommandAsync() { try { using (var proxy = new MyProxy()) { var something = await proxy.GetSomethingAsync(); } } catch (FaultException ex) { // Do something here } } public async override void MyCommandImplementation() { MyCommandAsync(); } 

通常,我建议使用async Task ExecuteAsync方法实现async ICommand ,并匹配async void Execute ,它只会执行await ExecuteAsync(); 。 我上面的例子几乎是相同的,除了async void方法没有 await Task 。 这很危险,我将在下面解释。

将逻辑保存在async Task会给您带来巨大的优势:您可以更轻松地进行unit testing。 此外, async Task方法具有不同的exception处理,您可以(ab)使用它来解决您的问题。

async Task方法 – 如果从未await返回的Task – 将引发TaskScheduler.UnobservedTaskException 。 请注意,这不会导致进程崩溃(从.NET 4.5开始); 您的处理程序必须决定最佳响应。 由于您的async void方法 await async Task方法返回的async Task ,因此任何exception都将以UnobservedTaskException结束。

这样可行,但它有一个严重的副作用: 任何未观察到的Taskexception将最终出现在同一个处理程序中(不仅仅是来自ICommand的处理程序)。 默认情况下,在.NET 4.5中更改未观察到的任务exception的原因是因为async代码中的情况不再exception 。 例如,考虑这个代码,它将尝试从两个不同的URL下载并获取第一个响应:

 async Task GetMyStringAsync() { var task1 = httpClient.GetAsync(url1); var task2 = httpClient.GetAsync(url2); var completedTask = await Task.WhenAny(task1, task2); return await completedTask; } 

在这种情况下,如果较慢的url导致错误,那么该exception将被发送到UnobservedTaskException

我目前正在进行面向方面的编程。 所以我使用Postsharps方法拦截方面进行服务调用。 它允许您集中用于调用服务的代码等。 我还将它用于日志记录和线程同步。

编辑:我刚刚发现await关键字尚不支持。 (进入3.1)。

这是一个例子:

 [Serializable] internal class ServiceCall : MethodInterceptionAspect { public override void OnInvoke(MethodInterceptionArgs args) { try { args.Proceed(); } catch (FaultException f) { showError(f.Detail.Message + "\r\n" + f.Detail.Callstack, f.Detail.Title); } catch (Exception e) { showError(e.Message, "Error"); } } 

以下是它的使用方法

 [ServiceCall] public Something getSomethingAsync() { return await _client.getSomethingAsync(); } 

方面也可以应用于整个类或程序集。