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.
|
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) {}
}
}
|