I must be losing it...
Hi,
I'm having trouble spawning a new thread using the EventQueue or SwingUtilities invokeLater() static methods.
When you call SwingUtilities.invokeLater(Runnable runner), the result is supposed to be that the runner is executed in a new Thread, and your app keeps chugging right along, right? I know I've read this in more than one place.
So if I run code like the following:
long startTime = System.currentTimeMillis();
SwingUtilities.invokeLater(new Runnable(){
publicvoid run(){
startReallyLongProcess();
}
});
long totalTime = System.currentTimeMillis() - startTime;
System.out.println("Spawning new Thread took " + totalTime +" milliseconds");
...I should see a very small value for the number of milliseconds it took to spawn the new thread. Am I right about this?
I've read that the only proper way of updating the state of GUI components is on the Event Dispatch Thread, and the way of getting this to happen is by not running your threads manually, but instead handing them to SwingUtilities.invokeLater(). When I just spawn threads manually and update my GUI off of the EDT, everything works correctly. But I've been warned that this can lead to problems of some sort.
So, I'm not too sure what to do at this point.
null
[1657 byte] By [
nordmanna] at [2007-10-2 22:05:10]

> Hi,
>
> I'm having trouble spawning a new thread using the
> EventQueue or SwingUtilities invokeLater() static
> methods.
>
> When you call SwingUtilities.invokeLater(Runnable
> runner), the result is supposed to be that the runner
> is executed in a new Thread, and your app keeps
> chugging right along, right?
Wrong. You code is executed on the EventDispatch thread. The method name 'invokeLater' describes exactly what the method does. It invokes the code at some later time. If it spawned a new thread, the method would be something like invokeOnNewThread.
Here is the JavaDoc from invokeLater:
========
invokeLater
public static void invokeLater(Runnable doRun)
Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread.
The purpose of invokeLater is to allow you to perform updates to your GUI *from* another thread.
> I know I've read this in more than one place.
Then those places are all wrong. :)
>
> So if I run code like the following:
>
> > long startTime = System.currentTimeMillis();
> SwingUtilities.invokeLater(new Runnable() {
> public void run() {
> startReallyLongProcess();
> }
> });
> long totalTime = System.currentTimeMillis() -
> startTime;
> System.out.println("Spawning new Thread took " +
> totalTime + " milliseconds");
>
> ...I should see a very small value for the number of
> milliseconds it took to spawn the new thread. Am I
> right about this?
You haven't provided enough context. If this code is running inside the EDT, such as part of an ActionListener, then no. Your code will wait for all pending events to be handled, but then it will tie up the EDT until it is finished. So, if the EDT had three pending events: A, B and C, each of those would be handled, and then your code may or may not run before the call to System.currenttimeMillis().
>
> I've read that the only proper way of updating the
> state of GUI components is on the Event Dispatch
> Thread, and the way of getting this to happen is by
> not running your threads manually, but instead
> handing them to SwingUtilities.invokeLater().
Not quite. You can create all the threads you want, but when the time comes to *manipulate* a Swing component, you should do it using invokeLater.
> When I
> just spawn threads manually and update my GUI off of
> the EDT, everything works correctly.
It only seems to work correctly.
> But I've been warned that this can lead to problems of some sort.
Yep.
>
> So, I'm not too sure what to do at this point.
>
Have a look at the following example. It creates two labels and two buttons. Each button has an ActionListener, and one ActionListener is implemented the right way and the other is implemented the wrong way. Each is supposed to change the corresponding label text to dispay the current tick count and then sleep() for 1 second. Think of the calls to sleep() as though they were some long running code. This cycle should repeat for 5 seconds.
Click on the good button and it immediately returns to the 'unclicked' state and the label is dutifully updated every second for five seconds.
Click on the bad button and it stays stuck in the 'pressed' state. No change to the label occurs. After 5 seconds the button returns to the correct state and the label goes right from 1 to 5.
The 'good' button starts a new thread that updates the label text using invokeLater().
The 'bad' button starts a loop inside invokeLater() which updates the label text.
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class InvokeLaterTest {
public InvokeLaterTest() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JLabel leftLabel = new JLabel("Ticks: ");
final JLabel rightLabel = new JLabel("Ticks: ");
JButton goodButton = new JButton("Good");
JButton badButton = new JButton("Bad");
goodButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
Thread t = new Thread(new Runnable() {
public void run() {
int count = 1;
while(count <= 5) {
final int copyCount = count;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
leftLabel.setText("Ticks: " + copyCount);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
count++;
}
}
});
t.start();
}
});
badButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int count = 1;
while (count <= 5) {
rightLabel.setText("Ticks: " + count);
try {
Thread.sleep(1000);
count++;
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
});
}
});
JPanel labelPanel = new JPanel(new GridLayout(1,0));
labelPanel.add(leftLabel);
labelPanel.add(rightLabel);
JPanel buttonPanel = new JPanel(new GridLayout(1,0));
buttonPanel.add(goodButton);
buttonPanel.add(badButton);
f.add(labelPanel, BorderLayout.CENTER);
f.add(buttonPanel, BorderLayout.SOUTH);
f.setSize(400,300);
f.setVisible(true);
}
public static void main(String[] args) {
new InvokeLaterTest();
}
}