【问题标题】:Exception handling inside "async void" WPF command handlers“async void”WPF 命令处理程序中的异常处理
【发布时间】:2014-02-09 11:44:37
【问题描述】:

我正在查看我同事的一些 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 事件处理程序。

【问题讨论】:

  • This MSDN thread 可能会对您的问题有所了解
  • @ElliotTereschuk,谢谢,该线程引用了another good post。然而,AwaitableDelegateCommand.Execute 从那里仍然没有传播任何内部异常,AFAICT。所以我不确定我们是否可以将它与 XAML 声明性命令绑定一起使用。
  • @Noseratio,你为什么不在 catch 中触发事件表单?为什么会有额外的变量?为什么要向通知发送ExceptionDispatchInfo 而不是异常?
  • 你为什么不在 catch 内部触发事件表单? 我在 catch 外部触发它们,因为 AsyncErrorEvent 处理程序也可能抛出。我不希望他们扔进我的catch,因为原始异常会丢失,更多信息:thisthis
  • @PauloMorgado,这是一个UserControl 库,它对 ViewModal 或其外部的任何其他客户端代码一无所知。通过触发这样的错误事件,我让这样的代码有机会以任何所需的方式处理/报告/记录错误。客户端代码的开发人员可以选择通过AsyncErrorEventArgs.ExceptionInfo.SourceException 检查异常或通过AsyncErrorEventArgs.ExceptionInfo.Throw() 重新抛出异常,或者两者都做。 如果您有其他解决方案,请随时发布答案,这就是问题所要求的。

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


【解决方案1】:

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

至于 UI 内部抛出的异常,我再次觉得存在架构问题。如果 UserControl 不仅仅是充当视图(即运行可能导致意外异常的任何类型的业务逻辑),则应将其分为视图和视图模型。 ViewModel 将运行逻辑,并且可以由您的其他应用程序 ViewModel 实例化,或者通过其他方法进行通信(如上所述)。

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

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

【讨论】:

  • 另一个层次的分离确实有意义,+1。然而,从形式上看,问题仍然存在于 ViewModel 中。例如,让我们考虑 NewCommand_Executed 是 ViewModal 的一部分,并且控件公开了在 NewCommand_Executed 内部调用的 LoadAsync API。
  • 我想我现在明白了。然后 ViewModal 可以在 async void 处理程序中执行简单的 try/catch 并以它认为合适的方式报告错误。我会暂时搁置这个问题,但我认为赏金是你的,当之无愧,谢谢!
  • 据我了解,UserControls应该包含RoutedUICommands 的CommandBinding 代码,例如ApplicationCommands.Paste,这与您在第一段中所说的相反。你能澄清一下吗?
  • @JoeGaggler 你当然是正确的,纯粹的影响应用程序命令(如粘贴)的 UI 可以直接在用户控件中绑定和处理。我的观点是,任何使用异步或可能引发异常(可以处理)的命令都不应该。
  • 未来参考的相关链接:Commands, RelayCommands and EventToCommand.
【解决方案2】:

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

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

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

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

【讨论】:

  • 我确实想让用户意识到错误,但我不想将错误消息 UI 封装在控件中Task 作为控件属性与事件方法的问题在于它可能必须是List&lt;Task&gt; 属性,因为异步命令可以重叠。我正在考虑第 3 个选项:将客户端 UI 错误报告对象注入到控件中(可以传递给构造函数,或设置为属性)。虽然这与事件方法没有太大区别。
  • 是的,你是对的。因此,您想知道是否有任何方法可以具体解决 WPF 的此类问题。因此,不幸的是,答案是否定的。
猜你喜欢
  • 2012-09-02
  • 1970-01-01
  • 1970-01-01
  • 2014-06-17
  • 1970-01-01
  • 1970-01-01
  • 2021-02-10
  • 2017-05-12
  • 1970-01-01
相关资源
最近更新 更多