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

时间:2013-10-03 11:39:32

标签: c# wpf multithreading exception-handling async-await

我有一个与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<T>个对象,因为它们就像事件处理程序一样。所以异常处理实际上非常简单,即我能够使用以下模式捕获从我的WCF服务抛出的任何异常:

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

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

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

private static void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
    // Exception handler that handles some common exceptions
    // such as FaultException<AuthenticationFaultDetail>
    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.
    }
}

由于FaultException模式以及async关键字之前/之后的同步上下文切换,所有内容都可以正常运行,因为await会在UI线程中抛出。

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

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

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

我现在能想到的最好的事情如下:

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<MyFaultDetail> ex)
        {
            // Do something here
        }
        catch (Exception ex)
        {
            // For other exceptions in any thread
            handler.HandleException(ex);
        }
    }
}

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

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

2 个答案:

答案 0 :(得分:2)

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

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

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

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

public async override void MyCommandImplementation()
{
  MyCommandAsync();
}

通常情况下,我建议使用async ICommand方法实施async Task ExecuteAsync,并匹配async void Execute await ExecuteAsync();。我上面的示例几乎相同,只是async void方法 await Task。这很危险,我将在下面解释。

将您的逻辑保持在async Task为您提供了一个巨大的优势:您可以更轻松地进行单元测试。此外,async Task方法具有不同的异常处理,您可以(ab)使用它来解决您的问题。

async Task方法 - 如果返回的Task永远不会await - 将会引发TaskScheduler.UnobservedTaskException。请注意,这将使您的进程崩溃(从.NET 4.5开始);你的经纪人必须决定最好的回应。由于您的async void方法 await Task方法返回async Task,因此任何例外情况都会在UnobservedTaskException中结束

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

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

在这种情况下,如果较慢的网址导致错误,则该异常将发送到UnobservedTaskException

答案 1 :(得分:1)

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

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

以下是一个例子:

[Serializable]
internal class ServiceCall : MethodInterceptionAspect
{
  public override void OnInvoke(MethodInterceptionArgs args)
  {
    try
    {
        args.Proceed();
    }
    catch (FaultException<DCErrorMessage> 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();
}

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

相关问题