1
votes

I'm using DelegateCommands (Prism) inside my ViewModels which I expose to the outside as ICommands.

The caviat is: DelegateCommand.Execute is implemented as Task Execute(...) whereas ICommand.Execute is implemented as simple 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).

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.

Is there any way to catch the thrown exception when using the DelegateCommand as 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.

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.

How should an application working with ICommand deal with async underlying calls swallowing the exception?

DelegateBase (from which DelegateCommand inherits) defines its Execute as async void Execute and then awaits its own Task Execute() call). So while calling ICommand.Execute I end up effectively calling an async void under the hood.

1
Can you expose something other than ICommand outside of your ViewModel? Something like IAsyncCommand which inherits from ICommand ? - Michael
@Michael DelegateCommand does implement ICommand. Thus I am not able to expose a DelegateCommand as a IAsyncCommand. - Jonas

1 Answers

3
votes

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 awaits the asynchronous command.

This means that the ICommand.Execute call is not swallowing the exception. 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.

When ICommand.Execute is called from the UI thread (i.e., 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). 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. 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. 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. This is unfortunate, and I wish that Prism had an IAsyncCommand that you could expose instead, but it is what it is. (FWIW, I always use my own AsyncCommand that does implement an IAsyncCommand).
  2. Make your unit tests async Task (not async void), and then await the command's execution naturally.
  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.

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. 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.

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. 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.