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

倖福魔咒の 提交于 2019-12-06 07:29:40

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:

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 ICommands with an async Task ExecuteAsync method and matching async void Execute which will just do await ExecuteAsync();. The example I have above is almost the same except the async void method is not awaiting the 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. Also, async Task methods have different exception handling which you can (ab)use to solve your problem.

An async Task method - if the returned Task is never awaited - will raise TaskScheduler.UnobservedTaskException. Note that this will not crash your process (as of .NET 4.5); your handler must decide the best response. Since your async void method is not awaiting the Task returned by your async Task method, any exceptions will end up in 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 ICommands). 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. For example, consider this code which will attempt to download from two different urls and take the first response:

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.

I'm currently into aspect oriented programming. So I'm using Postsharps method interception aspect for service calls. 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. (Coming in 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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!