Dispatcher.Invoke and distribution errors

I have a splash screen for WPF (using .net 4.5 and mvvmlight) that needs to perform various load operations in an asynchronous way, showing progress and occasionally requesting user input.

When prompted for input, I will create forms / dialogs from the user interface thread to call ShowDialog (with the splash screen as the parent) so that there are no problems with cross-threads. All this works fine, but if an error occurs while prompting for input, the resulting exception is lost.

The examples below are simply not consistent with MVVM for simplicity.

Here is my app.cs that installs the UI dispatcher and is ready to handle any unhandled dispatcher exceptions for error reporting:

public partial class App : Application { private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { e.Handled = true; System.Windows.Forms.MessageBox.Show("Exception Handled"); } private void Application_Startup(object sender, StartupEventArgs e) { GalaSoft.MvvmLight.Threading.DispatcherHelper.Initialize(); } } 

And here is my (very simplified) startup / splash screen:

  private void Window_ContentRendered(object sender, EventArgs e) { System.Windows.Forms.MessageBox.Show("Starting long running process..."); var t = System.Threading.Tasks.Task.Factory.StartNew(() => { //some kind of threaded work which decided to ask for user input. GalaSoft.MvvmLight.Threading.DispatcherHelper.UIDispatcher.Invoke(() => { //Show form for user input, launched on UIDispatcher so that it created on the UI thread for ShowDialog etc throw new Exception("issue in capturing input"); }); }); } 

So, I ask to enter the user through Invoke (because I want to wait for an answer), but even if I call work through the UIDispatcher, Application_DispatcherUnhandledException is never raised and the exception is thrown. What am I missing? The example uses a task for a job with a stream, but this also happens when using BeginInvoke (). Surely the work (and the exception raised) should happen on UIDispatcher?

UPDATE: Alternative demo (exception not handled) using BeginInvoke

 private void Window_ContentRendered(object sender, EventArgs e) { System.Windows.Forms.MessageBox.Show("Starting long running process..."); Action anon = () => { //some kind of threaded work which decided to ask for user input. GalaSoft.MvvmLight.Threading.DispatcherHelper.UIDispatcher.Invoke(() => { //Show form for user input, launched on UIDispatcher so that it created on the UI thread for ShowDialog etc throw new Exception("issue in capturing input"); }); }; anon.BeginInvoke(RunCallback, null); } private void RunCallback(IAsyncResult result) { System.Windows.Forms.MessageBox.Show("Completed!"); } 
+5
source share
1 answer

Using Task

The exception is handled by the job, so the DispatcherUnhandledException does not fire. This is because you are using the synchronous Dispatcher.Invoke method, which is almost always bad practice; you are wasting time in the pool thread thread, waiting for the user interface to perform some operation. You should prefer Dispatcher.BeginInvoke or (when using await ) Dispatcher.InvokeAsync .

In addition, it might be a good idea to register for the TaskScheduler.UnobservedTaskException event TaskScheduler.UnobservedTaskException that such exceptions can be logged (this only happens after the Task is garbage collected).

Finally, if you can use C # 5 or higher, I highly recommend taking a look at async / await. The above method can be rewritten as:

  private async void Window_ContentRendered(object sender, EventArgs e) { MessageBox.Show("Starting long running process..."); await Task.Run(() => { //some kind of threaded work throw new Exception("foo"); }); // code after the await will automatically be executed on the UI thread // the await will also propagate exceptions from within the task throw new Exception("issue in capturing input"); } 

Using Delegate.BeginInvoke

Here we also perform the operation in the thread pool, but the exception is handled by the async result object. I am preventing you from fully utilizing this old stream model (APM).

By the way, you can get an exception if you call the corresponding EndInvoke (which you should do anyway):

  private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { Action a = () => { Dispatcher.Invoke(() => { throw new Exception(); }); }; a.BeginInvoke(Callback, a); } private void Callback(IAsyncResult ar) { ((Action)ar.AsyncState).EndInvoke(ar); } 

But even then, a DispatcherUnhandledException will not be thrown because the callback is executed on the thread pool thread. So the process is just crashing.

Conclusion

Using synchronous Dispatcher.Invoke will always propagate the exception to the caller. It is also very wasteful to use. If the caller is not a UI thread, the exception will never reach the dispatcher, and depending on the streaming API used, it will either be swallowed or thrown, and the process will fail.

+2
source

All Articles