2

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

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

由于我遵循 MVVM 模式,ICommand因此我的 ViewModel 公开了公共属性,因此关联的命令实现不会返回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 错误,一切都会按预期工作。

由于我有一些可以在我的服务中随处抛出的常见异常(例如,每个 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在 UI 线程中被抛出。asyncawait

我的问题是,除了我的 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 服务时)。

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

4

2 回答 2

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 ICommands ,这样就可以了。我上面的例子几乎相同,只是方法不是. 这很危险,我将在下面解释。async Task ExecuteAsyncasync void Executeawait ExecuteAsync();async void awaitTask

保持你的逻辑async Task给你一个巨大的优势:你可以更容易地进行单元测试。此外,async Task方法具有不同的异常处理,您可以(ab)使用它们来解决您的问题。

一个async Task方法(如果返回Task的从未await编辑过)将引发TaskScheduler.UnobservedTaskException。请注意,这不会使您的进程崩溃(从 .NET 4.5 开始);您的处理程序必须决定最佳响应。由于您的async void方法不是 await您的方法Task返回的async Task,因此任何异常都将以UnobservedTaskException.

所以这会起作用,但它有一个严重的副作用:任何未观察到Task的异常都将最终出现在同一个处理程序中(不仅仅是来自你ICommand的 s 的)。在 .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;
}

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

于 2013-10-03T20:48:39.953 回答
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();
}

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

于 2013-10-03T12:52:35.970 回答