[英]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的下一版本中删除。 如果单元测试框架不提供上下文( 应该是这样,并会在未来的情况下),那么异常会被线程池背景下,这将导致测试中的任意线程重新升起赛跑者失败。 测试运行程序对此的响应方式是不确定的:实际上,如果您只有一个测试,则测试运行程序可能会在看到异常之前完成,因此它实际上似乎是“丢失”的。 测试运行程序也有可能会忽略无法与特定测试匹配的异常。
相反,解决方案有两个方面:
DelegateCommand
类型而不是ICommand
类型。 不幸的是,我希望Prism可以公开一个IAsyncCommand
,但事实就是如此。 (FWIW,我始终使用自己的AsyncCommand
来实现IAsyncCommand
)。 async Task
(不是async void
),然后自然地await
命令的执行。 Execute
(而不是使用命令绑定),那么也应将其更新为async Task
(或async Task<T>
)并await
Execute
返回的任务。 请注意, ICommand.Execute
中的异常在运行时不会被忽略,但其效果与从事件处理程序引发的异常相同:如果要完全处理,则必须全局处理。 这通常不是您想要的。 对于异步命令,这尤其是一个问题,因为它们通常涉及易于发生错误的I / O操作,这些错误需要您妥善处理 。
要解决此“元问题”,您将需要重新审视异步命令的行为。 只是将try
/ catch
放在委托的顶部,并在失败时更新数据绑定的属性,这种情况并不罕见。 我在MSDN上有关“异步MVVM命令”的文章中探索了各种类似的解决方案,但是在这种情况下,“一个大小适合所有人”当然不适用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.