简体   繁体   English

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

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

I'm using DelegateCommands (Prism) inside my ViewModels which I expose to the outside as ICommands. 我在ViewModels中使用DelegateCommands(Prism),并将其作为ICommands暴露给外部。

The caviat is: DelegateCommand.Execute is implemented as Task Execute(...) whereas ICommand.Execute is implemented as simple void Execute(...). 鱼子酱是:DelegateCommand.Execute实现为Task Execute(...),而ICommand.Execute实现为简单的void Execute(...)。

I noticed this, because exceptions were swallowed in the execute handler. 我注意到了这一点,因为在执行处理程序中吞没了异常。 While it is a typical behaviour for asyncs which are not awaited I did not expect this to occur for ICommand.Execute (which has no sign of being async). 虽然这是等待异步的典型行为,但我没想到ICommand.Execute(没有异步迹象)会发生这种情况。

If I execute the ICommand I will not be able to catch eventually thrown exceptions by the DelegateCommand since DelegateCommands Execute() method is async whereas ICommands is not. 如果执行ICommand,我将无法捕获DelegateCommand最终引发的异常,因为DelegateCommands的Execute()方法是异步的,而ICommands则不是。

Is there any way to catch the thrown exception when using the DelegateCommand as ICommand? 使用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
}

Making the nUnit test case as async works, but visual studio complains that I have an async method without await anything as await ICommand.Execute is not possible. 使nUnit测试用例作为异步工作,但是Visual Studio抱怨我有一个异步方法,没有任何await ICommand.Execute

Casting it explicitly to DelegateCommand would be possible but this would fix only the unit test and not the application's behaviour when an exception is thrown. 可以将其显式转换为DelegateCommand,但这将仅修复单元测试,而不修复抛出异常时应用程序的行为。

How should an application working with ICommand deal with async underlying calls swallowing the exception? 使用ICommand的应用程序应如何处理吞下异常的异步基础调用?

DelegateBase (from which DelegateCommand inherits) defines its Execute as async void Execute and then awaits its own Task Execute() call). DelegateBase(DelegateCommand继承自DelegateBase)将其Execute定义为async void Execute ,然后等待其自己的Task Execute()调用)。 So while calling ICommand.Execute I end up effectively calling an async void under the hood. 因此,在调用ICommand.Execute时,我最终实际上是在后台调用了一个异步void。

exceptions were swallowed in the execute handler. 异常在执行处理程序中被吞没。

They certainly should not have been. 他们当然不应该如此。 According to the source code , ICommand.Execute is (correctly) implemented as an async void method that await s the asynchronous command. 根据源代码ICommand.Execute被(正确地)实现为await异步命令的async void方法。

This means that the ICommand.Execute call is not swallowing the exception. 这意味着ICommand.Execute调用不会吞下该异常。 However, it also cannot be caught directly, because it is an asynchronous method. 但是,由于它异步方法,因此也不能直接捕获。 I describe what happens in detail in my Async Best Practices article : in this case, the exception is re-raised within the context of the original call to ICommand.Execute . 我在Async Best Practices文章中详细描述了发生的情况:在这种情况下,在对ICommand.Execute的原始调用的上下文中重新引发了异常。

When ICommand.Execute is called from the UI thread (ie, by an MVVM binding), then that exception is raised on the UI thread, and whatever default behavior for that UI framework takes it from there (usually there's a last-chance handler followed by a dialog/modal). 当从UI线程(即通过MVVM绑定)调用ICommand.Execute则在UI线程上引发该异常,并且该UI框架的任何默认行为都将其带到那里(通常,后面有一个最后机会处理程序)通过对话框/模式)。 But when it's called from a unit test, it uses whatever context is provided by the unit test framework. 但是,当从单元测试中调用它时,它将使用单元测试框架提供的任何上下文。 I describe async unit testing further in another MSDN article , but the gist of it is this: if you make your unit test async void , then (the current version of) NUnit will give you a context. 我将在另一篇MSDN文章中进一步描述异步单元测试,但是要点是:如果使单元测试async void ,则NUnit(的当前版本)将为您提供上下文。 But don't rely on this behavior; 但是不要依赖这种行为。 it has already been recognized as a poor design decision and will be removed from the next version of NUnit v3. 它已经被认为是一个糟糕的设计决策,将从NUnit v3的下一版本中删除。 If the unit test framework does not provide a context (which should be the case, and will be the case in the future), then the exception will be re-raised on the thread pool context, which will cause an arbitrary thread within the test runner to fail. 如果单元测试框架提供上下文( 应该是这样,并在未来的情况下),那么异常会被线程池背景下,这将导致测试中的任意线程重新升起赛跑者失败。 How the test runner responds to this is indeterminate: in fact, if you only have one test, it is possible that the test runner will finish before the exception is seen, so it would indeed appear to be "lost". 测试运行程序对此的响应方式是不确定的:实际上,如果您只有一个测试,则测试运行程序可能会在看到异常之前完成,因此它实际上似乎是“丢失”的。 It is also possible that the test runner will ignore exceptions that it can't match to a specific test. 测试运行程序也有可能会忽略无法与特定测试匹配的异常。

Instead, the solution is two-fold: 相反,解决方案有两个方面:

  1. Expose your ViewModel properties as type DelegateCommand instead of ICommand . 将您的ViewModel属性公开为DelegateCommand类型而不是ICommand类型。 This is unfortunate, and I wish that Prism had an IAsyncCommand that you could expose instead, but it is what it is. 不幸的是,我希望Prism可以公开一个IAsyncCommand ,但事实就是如此。 (FWIW, I always use my own AsyncCommand that does implement an IAsyncCommand ). (FWIW,我始终使用自己的AsyncCommand来实现IAsyncCommand )。
  2. Make your unit tests async Task (not async void ), and then await the command's execution naturally. 使您的单元测试async Task (不是async void ),然后自然地await命令的执行。
  3. If any of your code is calling Execute directly (instead of using command bindings), then it should also be updated to be async Task (or async Task<T> ) and to await the task returned from Execute . 如果您的任何代码直接调用Execute (而不是使用命令绑定),那么也应将其更新为async Task (或async Task<T> )并await Execute返回的任务。

Note that the exception in ICommand.Execute is not ignored at runtime, but it will have the same effect as an exception that is raised from an event handler: it must be handled globally if it is to be handled at all. 请注意, ICommand.Execute中的异常在运行时不会被忽略,但其效果与从事件处理程序引发的异常相同:如果要完全处理,则必须全局处理。 This is usually not what you want. 这通常不是您想要的。 This is particularly a problem with asynchronous commands, since they usually involve I/O operations that are prone to errors that you want to handle gracefully . 对于异步命令,这尤其是一个问题,因为它们通常涉及易于发生错误的I / O操作,这些错误需要您妥善处理

To solve this "meta-problem", you'll need to revisit how exactly you want your asynchronous commands to behave. 要解决此“元问题”,您将需要重新审视异步命令的行为。 It is not uncommon to just put a try / catch at the top of the delegate, and update data-bound properties if it does fail. 只是将try / catch放在委托的顶部,并在失败时更新数据绑定的属性,这种情况并不罕见。 I explore a variety of similar solutions in my MSDN article on Async MVVM Commands , but this is a case where "one size fits all" certainly does not apply. 我在MSDN上有关“异步MVVM命令”的文章中探索了各种类似的解决方案,但是在这种情况下,“一个大小适合所有人”当然不适用。

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

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