Why did dispatcher BeginInvoke fail where Control BeginInvoke succeed in C# Windows Forms app?

空扰寡人 提交于 2019-12-10 16:26:04

问题


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

回答1:


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.



来源:https://stackoverflow.com/questions/31713263/why-did-dispatcher-begininvoke-fail-where-control-begininvoke-succeed-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!