繁体   English   中英

当用作ICommand时,由ASYNC DelegateCommand.Execute()引发的捕获异常

[英]Catch Exception thrown by ASYNC DelegateCommand.Execute() when used as ICommand

我在ViewModels中使用DelegateCommands(Prism),并将其作为ICommands暴露给外部。

鱼子酱是:DelegateCommand.Execute实现为Task Execute(...),而ICommand.Execute实现为简单的void Execute(...)。

我注意到了这一点,因为在执行处理程序中吞没了异常。 虽然这是等待异步的典型行为,但我没想到ICommand.Execute(没有异步迹象)会发生这种情况。

如果执行ICommand,我将无法捕获DelegateCommand最终引发的异常,因为DelegateCommands的Execute()方法是异步的,而ICommands则不是。

使用DelegateCommand作为ICommand时,有什么方法可以捕获引发的异常?

[Test]
public void DelegateToICommandExecute()
{
    var dCommand = new DelegateCommand(() => { throw new Exception(); });
    ICommand command = dCommand;
    command.Execute(null); // Doesn't fail due to exception
}

使nUnit测试用例作为异步工作,但是Visual Studio抱怨我有一个异步方法,没有任何await ICommand.Execute

可以将其显式转换为DelegateCommand,但这将仅修复单元测试,而不修复抛出异常时应用程序的行为。

使用ICommand的应用程序应如何处理吞下异常的异步基础调用?

DelegateBase(DelegateCommand继承自DelegateBase)将其Execute定义为async void Execute ,然后等待其自己的Task Execute()调用)。 因此,在调用ICommand.Execute时,我最终实际上是在后台调用了一个异步void。

异常在执行处理程序中被吞没。

他们当然不应该如此。 根据源代码ICommand.Execute被(正确地)实现为await异步命令的async void方法。

这意味着ICommand.Execute调用不会吞下该异常。 但是,由于它异步方法,因此也不能直接捕获。 我在Async Best Practices文章中详细描述了发生的情况:在这种情况下,在对ICommand.Execute的原始调用的上下文中重新引发了异常。

当从UI线程(即通过MVVM绑定)调用ICommand.Execute则在UI线程上引发该异常,并且该UI框架的任何默认行为都将其带到那里(通常,后面有一个最后机会处理程序)通过对话框/模式)。 但是,当从单元测试中调用它时,它将使用单元测试框架提供的任何上下文。 我将在另一篇MSDN文章中进一步描述异步单元测试,但是要点是:如果使单元测试async void ,则NUnit(的当前版本)将为您提供上下文。 但是不要依赖这种行为。 它已经被认为是一个糟糕的设计决策,将从NUnit v3的下一版本中删除。 如果单元测试框架提供上下文( 应该是这样,并在未来的情况下),那么异常会被线程池背景下,这将导致测试中的任意线程重新升起赛跑者失败。 测试运行程序对此的响应方式是不确定的:实际上,如果您只有一个测试,则测试运行程序可能会在看到异常之前完成,因此它实际上似乎是“丢失”的。 测试运行程序也有可能会忽略无法与特定测试匹配的异常。

相反,解决方案有两个方面:

  1. 将您的ViewModel属性公开为DelegateCommand类型而不是ICommand类型。 不幸的是,我希望Prism可以公开一个IAsyncCommand ,但事实就是如此。 (FWIW,我始终使用自己的AsyncCommand来实现IAsyncCommand )。
  2. 使您的单元测试async Task (不是async void ),然后自然地await命令的执行。
  3. 如果您的任何代码直接调用Execute (而不是使用命令绑定),那么也应将其更新为async Task (或async Task<T> )并await Execute返回的任务。

请注意, ICommand.Execute中的异常在运行时不会被忽略,但其效果与从事件处理程序引发的异常相同:如果要完全处理,则必须全局处理。 这通常不是您想要的。 对于异步命令,这尤其是一个问题,因为它们通常涉及易于发生错误的I / O操作,这些错误需要您妥善处理

要解决此“元问题”,您将需要重新审视异步命令的行为。 只是将try / catch放在委托的顶部,并在失败时更新数据绑定的属性,这种情况并不罕见。 我在MSDN上有关“异步MVVM命令”的文章中探索了各种类似的解决方案,但是在这种情况下,“一个大小适合所有人”当然不适用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM