全局捕获在后台线程中从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
,它将在客户端转换为FaultException
exception),我决定处理一些exception我的应用程序中的一个常见位置,即通过处理Application.DispatcherUnhandledException
事件。 这很好用,我可以在任何地方捕获所有FaultException
exception,向用户显示错误消息,并阻止应用程序退出:
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
结束。
这样可行,但它有一个严重的副作用: 任何未观察到的Task
exception将最终出现在同一个处理程序中(不仅仅是来自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(); }
方面也可以应用于整个类或程序集。