سؤال

I am working on understanding how to use BeginInvoke correctly. I wrote up a small test in a console app where all I am trying to do is use BeginInvoke to call a function to make a 100x100 Window with a title pop up. I am failing miserably. Here is what I have, I know this is probably just poor understanding of Threads (not my strong suit), but I'm stuck, no window pops up I just end up at my readline in Main waiting for a keypress. Execution starts at ThreadUITest.

static void ThreadUITest()
{
    ThreadStart starter = new ThreadStart(threadFunc1);
    Thread test = new Thread(starter);
    test.IsBackground = true;
    test.SetApartmentState(ApartmentState.STA);
    test.Start();
}

static void threadFunc1()
{
    dispatcher = Dispatcher.CurrentDispatcher; //Statically declared earlier
    ThreadStart starter = new ThreadStart(threadFunc2);
    Thread test = new Thread(starter);
    test.IsBackground = true;
    test.Start();
}

static void threadFunc2()
{
    Action method = Draw;
    Console.WriteLine("I'm here!");
    //dispatcher.BeginInvoke( (Action)(() => {Draw();}),DispatcherPriority.Render, null);
    dispatcher.BeginInvoke(method, DispatcherPriority.Send, null);
 }

 static void Draw()
 {
     Window win = new Window();
     win.Height = 100;
     win.Width = 100;
     win.Title = "A Window!";
     win.Show();
 }

Thanks for any help.

هل كانت مفيدة؟

المحلول 2

Try calling Dispatcher.Run() in the end of threadFunc1.

نصائح أخرى

You need to add the following at the bottom of your threadFunc1

// statically declared earlier, although you don't need to keep a reference to it as 
// WPF will keep it in Application.Current
application = new Application(); 
application.Run(); // thread1 is now our "UI" thread

Why does this solve it?

The Dispatcher object provides an interface for getting a thread to do some work for you (via BeginInvoke or Invoke).
In order for a thread to be able to process any "do work" messages, it must be running some kind of event loop, where it sits and waits for the next message to process - if it weren't doing this, then it wouldn't be able to process anything, it would just be stuck.

Calling Dispatcher.CurrentDispatcher from thread1 will create a new dispatcher on that thread if there isn't one already there[1] - that gives us our interface to post messages to the thread.
What dispatcher.BeginInvoke does is add an entry into the message queue for that thread, however the thread isn't running any message loop yet. We can queue messages to it, but it won't pick them up and run them - this is why nothing happens.

So, we need to make that thread start running a message loop.
The Application.Run() method is the WPF framework method which does exactly that. The Application.Run method never returns (until you call Application.Shutdown anyway), it starts up a message loop to begin processing messages thereafter. I find it useful to think of it "taking over" the thread.

Now with this change, when thread2func calls dispatcher.BeginInvoke, the message loop code inside Application.Run goes "oh look, a message, I'll process it" - it gets the BeginInvoke method, and does what it's told (in this case, executing your Draw function), and all is well


Note: As per Ark-kun's answer You can also just call Dispatcher.Run to start a message loop on that thread without creating an Application object (Application.Run does this internally). Generally I find it nicer to create an application object though, as that's more "normal", and some other code you may write later on may expect an Application object to exist


[1] FYI, this is why calling Dispatcher.CurrentDispatcher is dangerous and you should avoid it. If you call Dispatcher.CurrentDispatcher from the existing UI thread, it returns you a reference to the correct dispatcher.
If you accidentally call it from another thread, logically, you'd think it would return a reference to the existing dispatcher. But no - instead, it creates a second dispatcher, pointing at our other thread - however our other thread won't be running a message loop, and we'll get stuck again. I'd suggest never calling Dispatcher.CurrentDispatcher except for the very first time.

Once your app is up and running, you generally don't need to do this anyway, as all WPF objects (Window, Button, etc) all have a Dispatcher property which you can use to get the correct dispatcher from anyway

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top