问题
I have a WPF application that communicates with a WCF service. I'm currently calling my WCF service from my ViewModels (I'm using the MVVM pattern) using the following async
based pattern:
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. So the exception handling is actually pretty simple, i.e. I am able to catch any exception thrown from my WCF service using the following pattern:
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.
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, i.e. by handling the Application.DispatcherUnhandledException
event. 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:
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.
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();
line (case of WCF service shutdown on server side).
Those exceptions won't be handled in the Application.DispatcherUnhandledException
event handler because they are not thrown in the UI thread. 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).
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?
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).
Any idea that would allow me to not refactor a huge amount of code would be helpful.
回答1:
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 ICommand
s 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 await
ing 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 await
ed - 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 await
ing 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 ICommand
s). 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
.
回答2:
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.
来源:https://stackoverflow.com/questions/19158250/globally-catch-exceptions-thrown-from-wcf-async-calls-in-a-background-thread