我有一个与WCF服务通信的WPF应用程序。我正在使用以下基于async
的模式从我的ViewModel调用我的WCF服务(我正在使用MVVM模式):
public async override void MyCommandImplementation()
{
using (var proxy = new MyProxy())
{
var something = await proxy.GetSomethingAsync();
this.MyProperty = something;
}
}
当我遵循MVVM模式时,我的ViewModel公开了ICommand
个公共属性,因此关联的命令实现不会返回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 Fault的异常,一切都按预期工作。
因为我有一些常见的异常可以在我的服务中的任何地方抛出(例如,每个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
模式以及async
关键字之前/之后的同步上下文切换,所有内容都可以正常运行,因为await
会在UI线程中抛出。
我的问题是,可以在另一个线程中抛出其他异常而不是我的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服务)。
任何允许我不重构大量代码的想法都会有所帮助。
答案 0 :(得分: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 ICommand
方法实施async Task ExecuteAsync
,并匹配async void Execute
await ExecuteAsync();
。我上面的示例几乎相同,只是async void
方法不 await
Task
。这很危险,我将在下面解释。
将您的逻辑保持在async Task
为您提供了一个巨大的优势:您可以更轻松地进行单元测试。此外,async Task
方法具有不同的异常处理,您可以(ab)使用它来解决您的问题。
async Task
方法 - 如果返回的Task
永远不会await
- 将会引发TaskScheduler.UnobservedTaskException
。请注意,这将不使您的进程崩溃(从.NET 4.5开始);你的经纪人必须决定最好的回应。由于您的async void
方法不 await
Task
方法返回async Task
,因此任何例外情况都会在UnobservedTaskException
中结束
这样可行,但它有一个严重的副作用:任何未观察到的Task
异常将最终出现在同一个处理程序中(不只是来自ICommand
个的处理程序)。默认情况下,在.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;
}
在这种情况下,如果较慢的网址导致错误,则该异常将发送到UnobservedTaskException
。
答案 1 :(得分: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();
}
方面也可以应用于整个类或程序集。