“async void”WPF命令处理程序中的exception处理

我正在审查我的同事的一些WPF代码,这是一个基于UserControl的组件 ,包含许多async void事件和命令处理程序。 这些方法目前不在内部实现任何error handling

代码简而言之:

    
 private async void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e) { // do some fake async work (and may throw if timeout < -1) var timeout = new Random(Environment.TickCount).Next(-100, 100); await Task.Delay(timeout); } 

抛出exception但在NewCommand_Executed未观察到的exception只能在全局级别上处理 (例如,使用AppDomain.CurrentDomain.UnhandledException )。 显然,这不是一个好主意。

我可以在本地处理exception:

 private async void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e) { try { // do some fake async work (throws if timeout < -1) var timeout = new Random(Environment.TickCount).Next(-100, 100); await Task.Delay(timeout); } catch (Exception ex) { // somehow log and report the error MessageBox.Show(ex.Message); } } 

但是,在这种情况下,主机应用程序的ViewModel将不会意识到 NewCommand_Executed 的错误 。 也不是理想的解决方案,加上错误报告UI不应该总是作为库代码的一部分。

另一种方法是在本地处理它们并触发一个专门的错误事件:

 public class AsyncErrorEventArgs: EventArgs { public object Sender { get; internal set; } public ExecutedRoutedEventArgs Args { get; internal set; } public ExceptionDispatchInfo ExceptionInfo { get; internal set; } } public delegate void AsyncErrorEventHandler(object sender, AsyncErrorEventArgs e); public event AsyncErrorEventHandler AsyncErrorEvent; private async void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e) { ExceptionDispatchInfo exceptionInfo = null; try { // do some fake async work (throws if timeout < -1) var timeout = new Random(Environment.TickCount).Next(-100, 100); await Task.Delay(timeout); } catch (Exception ex) { // capture the error exceptionInfo = ExceptionDispatchInfo.Capture(ex); } if (exceptionInfo != null && this.AsyncErrorEvent != null) this.AsyncErrorEvent(sender, new AsyncErrorEventArgs { Sender = this, Args = e, ExceptionInfo = exceptionInfo }); } 

我最喜欢最后一个,但我很欣赏任何其他建议,因为我对WPF的体验有限。

  • 是否有已建立的WPF模式将错误从async void命令处理程序传播到ViewModal?

  • 在WPF命令处理程序中进行异步工作通常是一个坏主意 ,因为它们可能用于快速同步UI更新?

我在WPF的上下文中问这个问题,但我认为它也适用于WinForms中的async void事件处理程序。

这里的问题是您的UserControl库没有以典型的MVVM方式构建。 通常,对于非平凡的命令,您的UserControl的代码不会直接绑定到命令,而是具有在设置(通过绑定到ViewModel)时将触发控件中的操作的属性。 然后,您的ViewModel将绑定到应用程序命令,并设置适当的属性。 (或者,您的MVVM框架可能具有另一个消息传递方案,可用于ViewModel和View之间的交互)。

对于在UI中抛出的exception,我再次感到存在架构问题。 如果UserControl不仅仅充当View,(即运行可能导致意外exception的任何类型的业务逻辑),那么这应该分为View和ViewModel。 ViewModel将运行逻辑,可以由您的其他应用程序ViewModel实例化,也可以通过其他方法进行通信(如上所述)。

如果UserControl的布局/可视化代码抛出exception,则ViewModel不会以任何方式捕获(几乎无例外)。 正如您所提到的,这应该仅由全局级别处理程序进行日志记录处理。

最后,如果Control的代码中确实存在已知的“exception”,则需要通知您的ViewModel,我建议捕获已知exception并引发事件/命令并设置属性。 但同样,这不应该用于例外,只是预期的“错误”状态。

在我看来,用户几乎100%不知道的exception的传播并不是一个好习惯。 看到这个

我看到了你真正拥有的两个选项,因为WPF没有提供任何开箱即用的机制来通知任何问题:

  1. 你已经提供捕捉和解雇事件的方式。
  2. 从异步方法返回Task对象(在您的情况下,您似乎必须通过属性公开它)。 用户将能够检查执行期间是否存在任何错误,并在需要时附加延续任务。 在处理程序内部,您可以捕获任何exception并使用TaskCompletionSource来设置处理程序的结果。

总而言之,你必须为这样的代码编写一些xml-comments,因为这并不容易理解。 最重要的是你不应该(几乎)从任何辅助线程抛出任何exception。