How does the event dispatch thread work?
Asked Answered
G

4

13

With the help of people on stackoverflow I was able to get the following working code of a simple GUI countdown (which just displays a window counting down seconds). My main problem with this code is the invokeLater stuff.

As far as I understand invokeLater, it sends a task to the event dispatching thread (EDT) and then the EDT executes this task whenever it "can" (whatever that means). Is that right?

To my understanding, the code works like this:

  1. In the main method we use invokeLater to show the window (showGUI method). In other words, the code displaying the window will be executed in the EDT.

  2. In the main method we also start the counter and the counter (by construction) is executed in another thread (so it is not in the event dispatching thread). Right?

  3. The counter is executed in a separate thread and periodically it calls updateGUI. updateGUI is supposed to update the GUI. And the GUI is working in the EDT. So, updateGUI should also be executed in the EDT. It is the reason why the code for the updateGUI is enclosed in invokeLater. Is that right?

What is not clear to me is why we call the counter from the EDT. Anyway, it is not executed in the EDT. It starts immediately, a new thread and the counter is executed there. So, why can we not call the counter in the main method after the invokeLater block?

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class CountdownNew {

    static JLabel label;

    // Method which defines the appearance of the window.   
    public static void showGUI() {
        JFrame frame = new JFrame("Simple Countdown");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        label = new JLabel("Some Text");
        frame.add(label);
        frame.pack();
        frame.setVisible(true);
    }

    // Define a new thread in which the countdown is counting down.
    public static Thread counter = new Thread() {
        public void run() {
            for (int i=10; i>0; i=i-1) {
                updateGUI(i,label);
                try {Thread.sleep(1000);} catch(InterruptedException e) {};
            }
        }
    };

    // A method which updates GUI (sets a new value of JLabel).
    private static void updateGUI(final int i, final JLabel label) {
        SwingUtilities.invokeLater( 
            new Runnable() {
                public void run() {
                    label.setText("You have " + i + " seconds.");
                }
            }
        );
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                showGUI();
                counter.start();
            }
        });
    }

}
Glucoprotein answered 20/3, 2010 at 19:34 Comment(2)
@Glucoprotein here is a more detailed discussion: #182816Eardrop
@Glucoprotein Note: your counter does not start on the EDT, it starts on the main thread. The counter updates the GUI through the updateGUI method which does the updates on the EDT (because of the call to invokeLater).Eardrop
E
16

If I understand your question correctly you're wonder why you can't do this:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
        }
    });
    counter.start();
}

The reason why you can't do it is because the scheduler makes no guarantees... just because you invoked showGUI() and then you invoked counter.start() doesn't mean that the code in showGUI() will be executed before the code in the run method of the counter.

Think of it this way:

  • invokeLater starts a thread and that thread is schedules an asynchronous event on the EDT which is tasked with creating the JLabel.
  • the counter is a separate thread that depends on the JLabel to exists so it can call label.setText("You have " + i + " seconds.");

Now you have a race condition: JLabel must be created BEFORE the counter thread starts, if it's not created before the counter thread starts, then your counter thread will be calling setText on an uninitialized object.

In order to ensure that the race condition is eliminated we must guarantee the order of execution and one way to guarantee it is to execute showGUI() and counter.start() sequentially on the same thread:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
            counter.start();
        }
    });
}

Now showGUI(); and counter.start(); are executed from the same thread, thus the JLabel will be created before the counter is started.

Update:

Q: And I do not understand what is special about this thread.
A: Swing event handling code runs on a special thread known as the event dispatch thread. Most code that invokes Swing methods also runs on this thread. This is necessary because most Swing object methods are not "thread safe": invoking them from multiple threads risks thread interference or memory consistency errors. 1

Q: So, if we have a GUI why should we start it in a separate thread?
A: There is probably a better answer than mine, but if you want to update the GUI from the EDT (which you do), then you have to start it from the EDT.

Q: And why we cannot just start the thread like any other other thread?
A: See previous answer.

Q: Why we use some invokeLater and why this thread (EDT) start to execute request when it's ready. Why it is not always ready?
A: The EDT might have some other AWT events it has to process. invokeLater Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread. This will happen after all pending AWT events have been processed. This method should be used when an application thread needs to update the GUI. 2

Eardrop answered 20/3, 2010 at 19:44 Comment(8)
invokeLater does not start a new thread. Rather it schedules the Runnable to run in the existing AWT event dispatch thread.Filings
@Steve thanks, I corrected the line. If you notice in the last paragraph in the Q/A I copied the documentation and it specifically states: "[invokeLater causes] doRun.run() to be executed asynchronously on the AWT event dispatching thread." My assessment of the race condition is true either way.Eardrop
There's no reason why you couldn't use invokeAndWait here, right? That would make the current thread wait until the runnable has executed.Seely
@Chris I think the OP had trouble identifying the race condition, but there are many solutions for this problem.Eardrop
The more I read about this the more confused I am. I agree that the scheduler does not guarantee WHEN the task is going to run, but according to the last section of (java.sun.com/products/jfc/tsc/articles/threads/threads1.html) "Events are dispatched in a predictable order", which leads me to think that we don't know WHEN, but showGUI is queued always before updateGUI and that order prevails. If so, starting the counter Thread outside EDT should not be a problem... what do you guys think?Phonotypy
@Josmas, the quote you provided comes in after this: "There are several advantages in executing all of the user interface code in a single thread..." meaning that within the GUI thread, the events are dispatched in a predictable way, but the counter is not started on the GUI thread. updateGUI is not executed in the same thread as the showGUI code, therefore the above quote does not apply.Eardrop
I don't think that is the issue here; after reviewing download.oracle.com/javase/6/docs/api/java/awt/EventQueue.html which guarantees order, and looking at the code again; When it gets to main, it hits invokeLater first, so showGUI is enqueued in the EDT (even if it's not run straight away). Then it does not really matter where you start the counter, cause updateGUI also calls invokeLater, meaning that it will always be enqueued after the previous call. So there is no chance that the gui can be updated before showing it, cause the queue at the EDT will prevent that.Phonotypy
@Phonotypy I know this question is from a long time ago, but I agree with you. Even if counter.start() is called outside of main's invokeLater() call, updateGUI (called within the counter thread) will invokeLater() on label.setText. As a result, this line of code will be invoked after showGUI() is called. Am I missing something here?Avogadro
T
2

You are actually starting the counter thread from the EDT. If you called counter.start() after the invokeLater block, the counter would likely start to run before the GUI becomes visible. Now, because you're constructing the GUI in the EDT, the GUI wouldn't exist when the counter starts to update it. Luckily you seem to be forwarding the GUI updates to the EDT, which is correct, and since the EventQueue is a queue, the first update will happen after the GUI has been constructed, so there should be no reason why this wouldn't work. But what's the point of updating a GUI that may not be visible yet?

Toady answered 20/3, 2010 at 19:43 Comment(7)
Thanks. What you say makes sense to me. So, if I start counter after the invokeLater after some delay, it should work?Glucoprotein
Actually I made a mistake in the first version of my answer. I think it should work both ways.Toady
@Glucoprotein Nope, it still wouldn't work... a time delay does not give you a guarantee, it merely buys you time and you still have the race condition. You don't want to mask your race conditions, you want to eliminate them.Eardrop
Joonas Pulakka, but why it should work both way? Where you made a mistake?Glucoprotein
@Joonas He starts the counter in the main thread, not in the EDT. The EDT and the main thread are two separate things.Eardrop
@Lirik: No, he does not. He starts the counter within the invokeLater block's run block, which takes place in the EDT.Toady
@Roman: Now that I looked more carefully; yes, I was wrong; it won't work if the counter is started after invokeLater. But the reason is that when updateGUI gets first called, even though the setText is executed in the EDT, the label reference is taken from the moment when the counter first calls updateGUI - that is, when updateGUI's Runnable is constructed, and on that moment label happens still to be null! Anyway, a better solution here would be using invokeAndWait.Toady
C
1

What is the EDT?

It's a hacky workaround around the great many concurrency issues that the Swing API has ;)

Seriously, a lot of Swing components are not "thread safe" (some famous programmers went as far as calling Swing "thread hostile"). By having a unique thread where all updates are made to this thread-hostile components you're dodging a lot of potential concurrency issues. In addition to that, you're also guaranteed that it shall run the Runnable that you pass through it using invokeLater in a sequential order.

Then some nitpicking:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
            counter.start();
        }
    });
}

And then:

In the main method we also start the counter and the counter (by construction) is executed in another thread (so it is not in the event dispatching thread). Right?

You don't really start the counter in the main method. You start the counter in the run() method of the anonymous Runnable that is executed on the EDT. So you really start the counter Thread from the EDT, not the main method. Then, because it's a separate Thread, it is not run on the EDT. But the counter definitely is started on the EDT, not in the Thread executing the main(...) method.

It's nitpicking but still important seen the question I think.

Corrianne answered 20/3, 2010 at 19:57 Comment(2)
WizardOfOdds, I understand that counter is not run on the EDT. It is run in a separate thread which (thread) is started from the EDT. I also understand that the counter is not run in the same thread as the main method. I just wanted to say that the main method sent showGUI and counter.start to the EDT and counter.start starts a new thread from EDT.Glucoprotein
@Roman: yup, exactly... But it's important to get the wording right in case someone else reads this question/answers :)Corrianne
T
0

This is simple, it is as follows

Step 1 . Initial thread also called main thread is created.

Step 2. Create a runnable object and pass it to invokeLate().

Step 3. This initialises the GUI but does not create the GUI.

Step 4. InvokeLater() schedules the created object for execution on EDT.

Step 5. GUI has been created.

Step 6. All events occurring will be placed in EDT.

Toneytong answered 20/4, 2014 at 21:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.