4

I originally tried to use the Dispatcher class BeginInvoke method to show a message box on the main UI thread in my C# Windows Forms app. When I used that method the message box did not appear. I set a breakpoint inside the body of the delegate I passed to BeginInvoke() and it was never hit. I tried using both an Action delegate and a MethodInvoker delegate. No luck in either case.

When I used the BeginInvoke method belonging to the Form object it worked fine. Why did the Dispatch version fail silently (no exceptions or error messages)? Below are the two different versions.

Dispatcher dispatcher = Dispatcher.CurrentDispatcher;

// THIS FAILED. CONTEXT: Executing on worker thread.
MethodInvoker theMethod = new MethodInvoker(delegate()
{
    string msg = "Show this  message on the main UI thread.";
    MessageBox.Show(msg, "Message");
});

dispatcher.BeginInvoke(theMethod);

this.BeginInvoke(theMethod);

// ---------------------------------------------------

// THIS WORKED. CONTEXT: Executing on worker thread.
MethodInvoker theMethod = new MethodInvoker(delegate()
{
    string msg = "Show this  message on the main UI thread.";
    MessageBox.Show(msg, "Message");
});

// "this" is a Form object.
this.BeginInvoke(theMethod);
Robert Oschler
  • 14,153
  • 18
  • 94
  • 227
  • Seems like this page contains an example of how to use `Dispatcher` with winforms: http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms – Loathing Jul 30 '15 at 00:24
  • I tried your `dispatcher.BeginInvoke(...);` code and it worked fine in .NET4. – Loathing Jul 30 '15 at 00:31
  • @Loathing Thanks for testing. I (more than) triple-checked during testing and I can assure you that for me it absolutely didn't work. So now we have a truly strange situation apparently. – Robert Oschler Jul 30 '15 at 00:39
  • 2
    @RobertOschler: There's already a good answer here, but as a side note: You shouldn't be using either of the `BeginInvoke` methods. To report progress, use `IProgress`. To update the UI with results, use `await`. To dynamically update the UI from an asynchronous stream, use Rx's `ObserveOn(SynchronizationContext)`. These newer ways decouple your logic from your UI and are unit testable. – Stephen Cleary Jul 30 '15 at 14:16
  • @StephenCleary Thanks Stephen. – Robert Oschler Jul 30 '15 at 17:33

1 Answers1

3

If I'm reading your comments correctly, you are calling Dispatcher.CurrentDispatcher from a non-UI thread. This is not how this is meant to be used.

As the documentation for Dispatcher.CurrentDispatcher says:

Gets the Dispatcher for the thread currently executing and creates a new Dispatcher if one is not already associated with the thread.

To get a valid dispatcher instance, you need to call Dispatcher.CurrentDispatcher from the UI thread.

Also, because the documentation says that it will automatically create a dispatcher if one does not exist for the current thread, this is what explains the silent failure. You are getting a dispatcher instance, but it isn't associated to the UI thread in any way, so it isn't actually dispatching anything to the UI thread.

(Removing this, because in my tests, I get null even when I shouldn't, so it doesn't prove much it seems. The rest of the information is accurate though) The documentation also adds:

This is not the case with the FromThread method. FromThread will return null if there is not a dispatcher associated with the specified thread.

So to confirm that you are indeed getting an auto-created (invalid) dispatcher, try getting the dispatcher from Dispatcher.FromThread instead. My guess is that you will get null.

If you want to call dispatcher.BeginInvoke to force execution of a method on the UI thread from a worker thread, you need to call Dispatcher.CurrentDispatcher from the UI thread and save that to a variable. You can then pass that dispatcher reference variable to the worker thread, and call BeginInvoke on that.

// capture and save dispatcher from UI thread
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;

// then you can do this from your worker thread:
dispatcher.BeginInvoke(theMethod);

Alternatively, use this.BeginInvoke like you are doing already.

Or better yet, you can try using tasks combined with the new async-await keywords for this sort of thing.

EDIT

For completeness, I should explain why Control.BeginInvoke does work correctly.

As the documentation for Control.BeginInvoke says:

Executes the specified delegate asynchronously on the thread that the control's underlying handle was created on.

And later it also adds:

You can call this method from any thread.

The point is that, when you call Control.BeginInvoke, it doesn't use the current thread to determine how to execute the delegate. It remembers which thread the control was created on (the UI thread), and makes sure to execute the delegate on that thread.

So, as long as your control is created on the UI thread (as it should), then BeginInvoke works from any thread. This is actually quite similar to the Dispatcher in that, as long as you obtain the Dispatcher instance from the UI thread first, then you can call Dispatcher.BeginInvoke from any thread as well.

sstan
  • 35,425
  • 6
  • 48
  • 66