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.