全局捕获后台线程中 WCF 异步调用引发的异常

2024-03-03

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

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 线程中async模式和同步上下文切换之前/之后await关键词。

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

任何能让我做到的想法not重构大量代码会很有帮助。


通常,我不太喜欢集中式/全局异常处理。我个人的偏好是要么处理各处的异常,要么编写自己的代理包装对象来处理/转换预期的错误异常。

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

首先,将实际逻辑分解为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方法是not awaiting the Task。这很危险,我将在下面解释。

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

An async Task方法 - 如果返回Task从来没有awaited - 将提高TaskScheduler.UnobservedTaskException http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.unobservedtaskexception.aspx。请注意,这将not使您的进程崩溃(从 .NET 4.5 开始);您的处理程序必须决定最佳响应。自从你的async void方法是not awaiting the Task由您返回async Task方法,任何异常都会以UnobservedTaskException.

这样会起作用,但它有一个严重的副作用:any未被观察到的Task异常将最终出现在同一个处理程序中(而不仅仅是来自您的处理程序的处理程序)ICommands)。 .NET 4.5 中将未观察到的任务异常更改为默认忽略的原因是这种情况已不再罕见 in 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.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

全局捕获后台线程中 WCF 异步调用引发的异常 的相关文章

随机推荐