Dispatcher.Invoke and propagating errors
Asked Answered
S

1

9

I have a splash screen for a WPF (using .net 4.5 and mvvmlight) that must perform various load operations in an async manner, showing progress and occasionally asking for user input.

When asking for input, I'll create forms/dialogs off the UI thread to call ShowDialog (with the splash screen as the parent) so that no cross-threading issues occur. This all works fine BUT if an error occurs when asking for input, the resulting exception is lost.

The examples below don't follow MVVM at all for simplicity.

Here is my app.cs, which set the UI dispatcher and is prepared 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's created on the UI thread for ShowDialog etc
                    throw new Exception("issue in capturing input");
                });
            });
        }

So I'm asking for user input through Invoke (because I want to wait for the answer) but even though I'm calling the work through the UIDispatcher, Application_DispatcherUnhandledException is never fired and the exception is lost. What am I missing? The example is using a Task for the threaded job but this also occurs when using BeginInvoke(). Surely the work (and resulting exception) should be occurring on the UIDispatcher?

UPDATE: Alternative demonstration (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's 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!");
        }
Sapp answered 23/6, 2016 at 12:7 Comment(0)
S
9

The exception is handled by the task, so DispatcherUnhandledException wouldn't fire. This is because you use the synchronous Dispatcher.Invoke method - which is almost always a bad practice; you're wasting time on a thread-pool thread waiting for the UI to perform some operation. You should prefer Dispatcher.BeginInvoke or (when using await) Dispatcher.InvokeAsync.

In addition, it may be a good idea to register for the TaskScheduler.UnobservedTaskException event, so that exceptions like these could be logged (this only happens after the Task is garbage collected).

Lastly, if you are able to use C# 5 or above, I strongly recommend taking a look at async/await. The above method could 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 a 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 threading API used, it will either be swallowed or thrown and crash the process.

Stupendous answered 23/6, 2016 at 12:15 Comment(3)
But in this case the thread is in fact waiting on UI, that's why I'm using Invoke. "Starup" is paused and waiting on input from the user. Also, I can reproduce this behavior while not using a task at all: anon.BeginInvoke(RunCallback, null) where anon is the anonymous method with the work.Sapp
Ok thanks. I think I understand how the error is already being handled in both Task and BeginInvoke examples. The purpose for all of this is a startup splash screen which performs load operations and shows progress/status to the UI in a thread safe manner. In most conditions, no input is needed and startup completes without touching the UI thread at all. But in some cases (simple example: the once-off authentication of a database) startup uses input from the user to complete it's operations. That's why a non-UI thread waits for UI input. Can you suggest a different mechanism for such cases?Sapp
Thanks I'll give this a go; we're updating a project over .net 4.5 so there's a fair amount of change needed in infrastructure.Sapp

© 2022 - 2024 — McMap. All rights reserved.