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

时间:2014-01-20 10:39:41

标签: c# .net wpf error-handling async-await

我正在审核我的同事的一些WPF代码,这是一个的基于UserControl的组件,其中有很多 async void 事件和命令处理程序。这些方法目前内部没有实现任何错误处理

代码简而言之:

<Window.CommandBindings>
    <CommandBinding
        Command="ApplicationCommands.New"
        Executed="NewCommand_Executed"/>
</Window.CommandBindings>
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);
}

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

我可以在本地处理异常:

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事件处理程序。

2 个答案:

答案 0 :(得分:7)

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

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

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

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

答案 1 :(得分:4)

在我看来,用户几乎100%不知道的异常的传播并不是一个好习惯。见this

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

  1. 你已经提供捕捉和解雇事件的方式。
  2. 从异步方法返回Task对象(在您的情况下,您似乎必须通过属性公开它)。用户将能够检查执行期间是否有任何错误,并在需要时附加延续任务。在处理程序内部,您可以捕获任何异常并使用TaskCompletionSource来设置处理程序的结果。
  3. 总而言之,你必须为这样的代码编写一些xml-comments,因为这并不容易理解。 最重要的是你不应该(几乎)从任何辅助线程中抛出任何异常。