invokeLater and InvokeAndWait
7.3 Using invokeLater( ) and invokeAndWait( )
In the CharacterDisplayCanvas class, we were able to work around Swing's threading restrictions because all the calls that manipulated Swing objects could go into an event callback method (the paintComponent() method). That's not always convenient (or even possible). So Swing provides another mechanism that allows you to run code on the event-dispatching thread: the invokeLater() and invokeAndWait() methods.
Which invokeLater( )? Which invokeAndWait( )?Java defines the invokeLater() and invokeAndWait() methods in two different classes: javax.swing.SwingUtilities and java.awt.EventQueue. This is due to historical reasons, and you can use whichever class you like. The methods are identical. The invokeLater() method of the SwingUtilities class simply calls the invokeLater() method of the EventQueue class, so they are functionally identical; the same is true of the two invokeAndWait() methods. |
The invokeLater() and invokeAndWait() methods allow you to define a task and ask the event-processing thread to perform that task. If you have a non-GUI thread that needs to read the value of a slider, for instance, you put the code to read the slider into a Runnable object and pass that Runnable object to the invokeAndWait() method, which returns the value the thread needs to read.
Let's look again at our score label class. The setScore() method of that class can be called when the user types a character (in which case it is running on the event-dispatching thread). It can also be called when the random character generator sends a new character. Therefore, the setScore() method must use the invokeLater() method to make that call:
package javathreads.examples.ch07.example1;
...
public class ScoreLabel extends JLabel implements CharacterListener {
...
private void setScore( ) {
SwingUtilities.invokeLater(new Runnable( ) {
public void run( ) {
setText(Integer.toString(score));
}
});
}
}
The invokeLater() method takes a Runnable object as its parameter. It sends that object to the event-dispatching thread, which executes the run() method. This is why it's always safe for the run() method to execute Swing code.
Note that the run() method is in its own object. This is why we made the score variable volatile rather than protecting it by using synchronization. Synchronizing the run() method grabs the lock of the anonymous inner class object, not the lock of the ScoreLabel object. It's much easier to use a volatile variable.
For the most part, the invokeAndWait() method looks similar, but it has three important semantic differences. First, the invokeLater() method runs asynchronously at some time in the future. You don't know when it will actually run. On the other hand, the invokeAndWait() method is synchronous: it does not return until its target has completed execution. As a rule of thumb, then, you should use the invokeAndWait() method to read the value of Swing components or to ensure that something is displayed on the screen before you continue program execution. Otherwise, you can use the invokeLater() method.
The second difference is that the invokeAndWait() method cannot itself be called from the event-dispatching thread. The thread running the invokeAndWait() method must wait for the event-dispatching thread to execute some code. No thread, including the event-dispatching thread, can wait for itself to do something else. Consequently, if you execute the invokeAndWait() method from the event-dispatching thread, it throws a java.lang.Error. That causes the event-dispatching thread to exit (unless you've taken the unusual step of catching Error objects in your code); in turn, your entire program becomes disabled.
The third difference is that the invokeAndWait() method can throw an InterruptedException if the thread is interrupted before the event-dispatching thread runs the target, or an InvocationTargetException if the Runnable object throws a runtime exception or error.
If you have code that you want to take effect immediately and that might be called from the event-dispatching thread, you can use the SwingUtilities.isEventDispatchThread() method to check the thread your code is executing on. You can then either call invokeAndWait() (if you're not on the event-dispatching thread) or call the Swing methods directly.
We could use that method in our ScoreLabel class like this:
package javathreads.examples.ch07.example2;
...
public class ScoreLabel extends JLabel implements CharacterListener {
...
private void setScore( ) {
if (SwingUtilities.isEventDispatchThread( ))
setText(Integer.toString(score));
else try {
SwingUtilities.invokeAndWait(new Runnable( ) {
public void run( ) {
setText(Integer.toString(score));
}
});
} catch (InterruptedException ie) {
} catch (InvocationTargetException ite) {}
}
}