12

I'm reviewing some WPF code of my colleagues, which is a library of UserControl-based components with a lot of async void event and command handlers. These methods currently do not implement any error handling internally.

The code in a nutshell:

<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);
}

Exceptions thrown but not observed inside NewCommand_Executed can only be handled on a global level (e.g., with AppDomain.CurrentDomain.UnhandledException). Apparently, this is not a good idea.

I could handle exceptions locally:

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);
    }
}

However, in this case the host app's ViewModel would be unaware of errors inside NewCommand_Executed. Not an ideal solution either, plus the error reporting UI shouldn't always be a part of the library code.

Another approach is to handle them locally and fire a dedicated error event:

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 });
}

I like the last one the most, but I'd appreciate any other suggestions as my experience with WPF is somewhat limited.

  • Is there an established WPF pattern to propagate errors from async void command handlers to ViewModal?

  • Is it generally a bad idea to do async work inside WPF command handlers, as perhaps they're intended for quick synchronous UI updates?

I'm asking this question in the context of WPF, but I think it may as well apply to async void event handlers in WinForms.

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    [This MSDN thread](http://social.msdn.microsoft.com/Forums/vstudio/en-US/cc8cd78c-df33-4c64-a01f-e10699d6bf6f/wpf-async-commands-in-view-model?forum=wpf) may shed some light on your problem – Ilya Tereschuk Jan 20 '14 at 10:43
  • @ElliotTereschuk, thank you, that thread references [another good post](http://jake.ginnivan.net/awaitable-delegatecommand). However, `AwaitableDelegateCommand.Execute` from there still doesn't propagate any inner exceptions, AFAICT. So I'm not sure if we can use that with XAML declarative command binding. – noseratio Jan 20 '14 at 11:53
  • @Noseratio, why aren't you firing the event form inside the catch? Why the extra variable? Why are you sending a `ExceptionDispatchInfo` to the notification instead of the exception? – Paulo Morgado Jan 22 '14 at 01:21
  • *Why aren't you firing the event form inside the catch?* I fire them outside catch, because `AsyncErrorEvent` handlers may throw too. I don't want them to throw inside my `catch`, as the original exception will be lost, more info: [this](http://stackoverflow.com/q/16626161/1768303) and [this](http://stackoverflow.com/q/19238521/1768303). – noseratio Jan 22 '14 at 01:41
  • *Why the extra variable?* For the reason described above. I want to do it outside `catch`. – noseratio Jan 22 '14 at 01:42
  • *Why are you sending a ExceptionDispatchInfo to the notification instead of the exception?* So I can re-throw it later with `ExceptionDispatchInfo.Throw()` on another stack frame and have the same debug information, more [info](http://stackoverflow.com/q/16626161/1768303). – noseratio Jan 22 '14 at 01:46
  • That's what I suspected, which leads to my next question: why would you throw an exception caught in the view on the view model? Who will catch it? – Paulo Morgado Jan 22 '14 at 07:37
  • 1
    @PauloMorgado, this a `UserControl` library which **knows nothing** about ViewModal, or any other client code external to it. By firing an error event like this, I give such code a chance to handle/report/log the error in any desired way. The developer of the client code may choose to examine the exception via `AsyncErrorEventArgs.ExceptionInfo.SourceException` or re-throw it via `AsyncErrorEventArgs.ExceptionInfo.Throw()` or do both. **Feel free to post an answer if you have an alternative solution**, that's what the question asks for. – noseratio Jan 22 '14 at 08:09
  • You don't mention if the exception is coming from the **ViewModel** or somewhere else. My view of things is that if it's coming from the **ViewModel**, whatever happened there that was needed there should already have been handled. If it's **UI** errors, they have no place in the **ViewModel**. If I'm bothering you too much, just say so and I will delete all my comments. – Paulo Morgado Jan 22 '14 at 12:25
  • @PauloMorgado, instead, maybe you can propose a better version of the 1st paragraph of my question. Does a `UserControl`-based component have anything to do with a ViewModel? I guess, as UI element, it does not. Just like a regular `Button` does. – noseratio Jan 22 '14 at 12:33
  • Tottaly agree. So why should the UI element send the exception to be re-thrown elsewhere? Unless you're expecting UI specific errors, every other error should should already have been taken care of. At least, that's the way I see it. – Paulo Morgado Jan 22 '14 at 16:38

2 Answers2

8

The issue here is that your UserControl library is not architected in a Typical MVVM way. Commonly, for non-trivial commands, your UserControl's code would not bind to commands directly, but instead would have properties that when set (through binding to a ViewModel) would trigger the action in the control. Then your ViewModel would bind to the application command, and set the appropriate properties. (Alternatively, your MVVM framework may have another message passing scenario that can be leveraged for interaction between the ViewModel and View).

As for Exceptions that are thrown inside the UI, I again feel that there is an architecture issue. If the UserControl is doing more than acting as a View, (i.e. running any kind of business logic that might cause unanticipated exceptions) then this should be separated into a View and a ViewModel. The ViewModel would run the logic and could either be instantiated by your other application ViewModels, or communicate via another method (as mentioned above).

If there are exceptions being thrown by the UserControl's layout / visualization code then this should (almost without exception) not be caught in any way by your ViewModel. This should, as you mentioned, only be handled for logging by a global level handler.

Lastly, if there truly are known 'exceptions' in the Control's code that your ViewModel needs to be notified about, I suggest catching the known exceptions and raising an event/command and setting a property. But again, this really shouldn't be used for exceptions, just anticipated 'error' states.

Andrew Hanlon
  • 7,271
  • 4
  • 33
  • 53
  • 1
    Another level of separation does make sense, +1. However, formally the problem would remain for ViewModel then. E.g, let's consider `NewCommand_Executed` is a part of ViewModal, and the control exposes `LoadAsync` API being called inside `NewCommand_Executed`. – noseratio Jan 24 '14 at 20:46
  • 1
    I guess I see now. Then ViewModal can do simple try/catch inside `async void` handler and report the errors the way it deems appropriate. I'll let the question hang for a while, but I think the bounty is yours and is well deserved, thanks! – noseratio Jan 24 '14 at 20:47
  • 2
    It's my understanding that UserControls _should_ include CommandBinding code for `RoutedUICommands` such as `ApplicationCommands.Paste`, contrary to what you said in the first paragraph. Could you clarify this? – JoeGaggler Jan 25 '14 at 02:16
  • @AndrewHanlon, could you please point us at some reputable MVVM resource about exposing dependency properties vs binding to app's commands inside `UserControl`? – noseratio Jan 27 '14 at 09:55
  • 2
    @JoeGaggler You are certainly correct that purely UI affecting application commands (like Paste) can be bound and handled in the usercontrol directly. My point is that any command that would use async or could throw an exception (that could to be handled) should not. – Andrew Hanlon Jan 27 '14 at 15:01
  • 2
    A relevant link for future references: [Commands, RelayCommands and EventToCommand](http://msdn.microsoft.com/en-us/magazine/dn237302.aspx). – noseratio Jan 30 '14 at 01:05
4

The propagation of exceptions about which the users are almost 100% unaware is not a good practice in my opinion. See this

I see the two options you really have since WPF doesn't provide any out of the box mechanisms of such the notifying of any problems:

  1. The way you already offered with catching and firing the event.
  2. Return the Task object from the async method (in your case, it seems that you will have to expose it through the property). The users will be able to check if there were any errors during the execution and attach a continuation task if they want. Inside the handler you can catch any exceptions and use TaskCompletionSource to set the result of the handler.

All in all you have to write some xml-comments for such a code, because that's not so easy to understand it. The most important thing is that you should never (almost) throw any exceptions from any secondary threads.

Community
  • 1
  • 1
EngineerSpock
  • 2,575
  • 4
  • 35
  • 57
  • I do want to make the user aware of the error, but **I don't want to encapsulate the error message UI inside the control**. The problem with the `Task` as a property of the control vs the event approach is that it probably has to be a `List` property, because **async commands can overlap**. I'm thinking about the 3rd option: inject the client UI error reporting object into the control (can be passed to the constructor, or set as a property). Although this is not much different from the event approach. – noseratio Jan 23 '14 at 04:19
  • Yes, you are right. So you wanted to know if there is any way to accomplish such a problem with WPF in specific. So the answer, for unfortune is no. – EngineerSpock Jan 23 '14 at 04:22