简体   繁体   English

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

[英]Globally catch exceptions thrown from WCF async calls in a background thread

I have a WPF application that communicates with a WCF service. 我有一个与WCF服务通信的WPF应用程序。 I'm currently calling my WCF service from my ViewModels (I'm using the MVVM pattern) using the following async based pattern: 我目前正在使用以下基于async的模式从ViewModels调用WCF服务(我正在使用MVVM模式):

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

As I'm following the MVVM pattern, I have ICommand public properties that are exposed by my ViewModels, so associated command implementation won't return Task<T> objects as they are like event handlers. 当我遵循MVVM模式时,我的ViewModels公开了ICommand公共属性,因此关联的命令实现不会像事件处理程序那样返回Task<T>对象。 So the exception handling is actually pretty simple, ie I am able to catch any exception thrown from my WCF service using the following pattern: 因此,异常处理实际上非常简单,即,我可以使用以下模式捕获从WCF服务抛出的所有异常:

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

So far so good, everything work as expected if the server throws an exception that is automatically converted to a SOAP Fault thanks to a custom WCF behavior. 到目前为止,如果服务器抛出一个由于自定义WCF行为而自动转换为SOAP Fault的异常,则一切正常。

As I have some common exceptions that can be thrown everywhere in my service (for instance each WCF operation can throw a AuthenticationException that'll by converted on client side to a FaultException<AuthenticationFaultDetail> exception), I've decided to handle some exceptions in a common place in my application, ie by handling the Application.DispatcherUnhandledException event. 由于我有一些常见的异常可以在服务中的任何地方抛出(例如,每个WCF操作都可以抛出AuthenticationException ,该异常将通过在客户端转换为FaultException<AuthenticationFaultDetail>异常),所以我决定处理一些异常在我的应用程序中的常见位置,即通过处理Application.DispatcherUnhandledException事件。 This works just fine, I can catch all my FaultException<AuthenticationFaultDetail> exceptions everywhere, display an error message to the user, and prevent the application from exiting: 这很好,我可以在所有地方捕获所有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.
    }
}

Everything works pretty well because FaultException are thrown in the UI thread thanks to the async pattern and the synchronization context switch before/after the await keyword. 一切都运行良好,因为在await关键字之前/之后,由于async模式和同步上下文切换,在UI线程中引发了FaultException

My problem is, other exception can be thrown in another thread than my UI thread, for example in case of EndPointNotFoundException thrown at the await proxy.GetSomethingAsync(); 我的问题是,可以在UI线程以外的其他线程中引发其他异常,例如,在await proxy.GetSomethingAsync();处抛出EndPointNotFoundException情况await proxy.GetSomethingAsync(); line (case of WCF service shutdown on server side). 行(服务器端WCF服务关闭的情况)。

Those exceptions won't be handled in the Application.DispatcherUnhandledException event handler because they are not thrown in the UI thread. 这些异常不会在Application.DispatcherUnhandledException事件处理程序中进行处理,因为它们不会在UI线程中引发。 I can handle them in the AppDomain.UnhandledException event, but I'm not able to do anything else than do some logging and exit the application (there is basically no "e.Handled"-like property). 我可以在AppDomain.UnhandledException事件中处理它们,但是除了记录日志并退出应用程序(基本上没有类似“ e.Handled”的属性)外,我无法执行其他任何操作。

So my question is: how could I handle exceptions thrown in background threads in case of an async WCF call in one place of my application? 所以我的问题是:在应用程序的某个位置发生异步WCF调用的情况下,如何处理在后台线程中引发的异常?

The best I can think of right now is something like the following: 我现在能想到的最好的东西如下所示:

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);
        }
    }
}

But this would require me to refactor a lot of code (each time I asynchronously call a web service). 但这需要我重构大量代码(每次我异步调用Web服务)。

Any idea that would allow me to not refactor a huge amount of code would be helpful. 任何允许我重构大量代码的想法都会有所帮助。

Normally, I'm not a big fan of centralized/global exception handling. 通常,我不喜欢集中式/全局异常处理。 My personal preference would be to either handle the exception everywhere or write your own proxy wrapper object that will handle/translate the expected fault exceptions. 我个人的喜好是要么在任何地方处理异常,要么编写自己的代理包装对象,以处理/转换预期的故障异常。

That said, there is an approach you can consider (though it requires modifying all your commands). 也就是说,有一种方法可以考虑(尽管它需要修改所有命令)。

First, factor the actual logic into an async Task method, as such: 首先,将实际逻辑分解为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();
}

Normally, I recommend implementing async ICommand s with an async Task ExecuteAsync method and matching async void Execute which will just do await ExecuteAsync(); 通常,我建议使用async Task ExecuteAsync方法实现async ICommand ,并匹配async void Execute ,它只会await ExecuteAsync(); . The example I have above is almost the same except the async void method is not await ing the Task . 我上面的示例几乎相同,除了async void方法 await Task This is dangerous and I'll explain below. 这很危险,我将在下面解释。

Keeping your logic in an async Task gives you one tremendous advantage: you can unit test much more easily. 使逻辑保持async Task为您提供了一个巨大的优势:您可以更轻松地进行单元测试。 Also, async Task methods have different exception handling which you can (ab)use to solve your problem. 另外, async Task方法具有不同的异常处理,您可以使用它们来解决问题。

An async Task method - if the returned Task is never await ed - will raise TaskScheduler.UnobservedTaskException . 一个async Task方法-如果返回的Task是永远await艾德-将提高TaskScheduler.UnobservedTaskException Note that this will not crash your process (as of .NET 4.5); 请注意,这不会使您的进程崩溃(从.NET 4.5开始); your handler must decide the best response. 您的经理必须决定最佳的回应。 Since your async void method is not await ing the Task returned by your async Task method, any exceptions will end up in UnobservedTaskException . 由于您的async void方法 await荷兰国际集团的Task由你返回async Task方式,任何异常都会在结束UnobservedTaskException

So that will work, but it has one serious side effect: any unobserved Task exception will end up in the same handler (not just ones from your ICommand s). 这样就可以了,但是有一个严重的副作用: 任何未观察到的Task异常都将在同一处理程序中结束(不仅仅是ICommand的处理程序)。 The reason that unobserved task exceptions were changed in .NET 4.5 to be ignored by default is because that situation is no longer unusual in async code. 未观察到的任务异常在.NET 4.5中被更改为默认情况下会被忽略的原因是, 这种情况async代码中不再常见 For example, consider this code which will attempt to download from two different urls and take the first response: 例如,考虑以下代码,该代码将尝试从两个不同的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;
}

In this case, if the slower url results in an error, then that exception will be sent to UnobservedTaskException . 在这种情况下,如果较慢的url导致错误,则该异常将发送到UnobservedTaskException

I'm currently into aspect oriented programming. 我目前正在面向方面的编程。 So I'm using Postsharps method interception aspect for service calls. 因此,我使用Postsharps方法拦截方面进行服务调用。 It allows you to centralize the code used for calling services among other things. 它使您可以集中用于调用服务的代码。 I also use it for logging and thread synchronization. 我还将它用于日志记录和线程同步。

Edit: I just found out that the await keyword is not yet supported. 编辑:我刚刚发现,尚不支持await关键字。 (Coming in 3.1). (来自3.1版)。

Here is an example: 这是一个例子:

[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");
    }
}

And here is how its used 这是它的用法

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

Aspects can also be applied to entire classes or assemblies. 方面也可以应用于整个类或程序集。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM