18.9 THE EVENT DISPATCH THREAD IN AWTSWING


18.9 THE EVENT DISPATCH THREAD IN AWT/SWING

We referred to the event processing loop in Chapter 17. This loop, executed in a separate thread in AWT/ssSwing, is called the Event Dispatch Thread.[13] Ideally, all of the interactions of a user with a GUI, such as when the user clicks on a button, or when the user chooses a menu item, and so on, should be handled by this thread.

The following program is a simple demonstration of the fact that ordinarily the event listener code is processed in the Event Dispatch Thread, whereas the code in main is executed in a separate thread. The program creates a window with a text area inside it. The user is allowed to enter text in the text area. (Since the GUI is so simple, a picture of the GUI is not shown.) To demonstrate which thread is handling what part of the code, we have essentially a do-nothing function keepBusy that is invoked both inside main in line (A) and inside the insertUpdate method of the MyDocumentListener class in line (B). The main purpose of keepBusy is to tell us which thread is executing what part of the program. This is accomplished with the help of the if block in line (C) where we test the boolean value of the predicate SwingUtilities.isEventDispatchThread().

 
//EventThreadDemo.java import java.awt. *; import java.awt.event. *; import javax.swing. *; import javax.swing.text. *; import javax.swing.event. *; class EventThreadDemo { public static void main(String[] args) { JFrame frame = new JFrame("Event Thread Demo"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit (0); } }); JTextArea textArea = new JTextArea(); textArea.setLineWrap(true); textArea.setWrapStyleWord(true); textArea.getDocument().addDocumentListener( new MyDocumentListener()); JScrollPane areaScrollPane = new JScrollPane(textArea); areaScrollPane.setVerticalScrollBarPolicy( JScrollPane.VERTICAL.SCROLLBAR_ALWAYS); areaScrollPane.setPreferredSize(new Dimension(250, 250)); areaScrollPane.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Plain Text"), BorderFactory.createEmptyBorder(5,5,5,5)), areaScrollPane.getBorder())); frame.getContentPane().add( areaScrollPane, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); keepBusy(500, "main"); //(A) } static class MyDocumentListener implements DocumentListener { public void insertUpdate(final DocumentEvent e) { String str = null; Document doc = e.getDocument(); int lengthText = doc.getLength(); try { str = doc.getText (lengthText -1, 1); } catch(BadLocationException badloc) { badloc.printStackTrace(); } keepBusy(500, "MyDocumentListener"); //(B) System.out.print(str); } public void removeUpdate (DocumentEvent e) { } public void changedUpdate(DocumentEvent e) { } } public static void keepBusy(int howLong, String source) { if (SwingUtilities.isEventDispatchThread() == true) //(C) System.out.printIn( //(D) " using Event Dispatch Thread for keepBusy in " + source); else System.out.printIn( //(E) " using the main thread for keepBusy in " + source); long curr = System.currentTimeMillis(); while (System.currentTimeMillis() < curr + howLong) ; } }

If you execute the class and enter the string "hello" in the text area of the GUI, lines (D) and (E) of the program will print out the following messages in the terminal:

       using the main thread for keepBusy in main     h using Event Dispatch Thread for keepBusy in MyDocumentListener     e using Event Dispatch Thread for keepBusy in MyDocumentListener     l using Event Dispatch Thread for keepBusy in MyDocumentListener     l using Event Dispatch Thread for keepBusy in MyDocumentListener     o using Event Dispatch Thread for keepBusy in MyDocumentListener 

Obviously, the code shown in the insertUpdate method of the MyDocumentListener class is being executed in the Event Dispatch Thread, while the code in main () itself is being executed in a separate thread.

Ordinarily, this would also be the case if we constructed a more complex GUI with multiple top-level components and multiple listeners. All of the code in all the listener methods would get executed in a single thread, the Event Dispatch Thread. The advantages of this are obvious-you do not run into synchronization issues, in the sense that you do not have to worry that the displayed text in a text area may appear as partially old and partially new.

One is naturally curious about the following situation: Suppose we launch multiple GUI windows at the top level, each in a separate thread of computation, would all of the event processing for all of the GUI's be carried in one and the same Event Dispatch Thread? The answer, as demonstrated by the following program, is yes-at least ordinarily.

The following program launches separate GUI's, each in its own thread of computation, in lines (A) and (B) of the program. Each GUI looks the same as in the previous program-it is a window with a text area for the user to enter characters into. The program has also been provided with a utility class MyTools in line (L). Its method printThreadInfo in line (M) prints out the name of the thread in which the method is invoked. This method is invoked at various places in the program, such as in lines (G), (I), (J), and so on, to determine the identity of the thread executing the code at that point and also in line (O) of the MyDocumentListener class.

 
//EventThreadDemo2.java import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.text.*; import javax.swing.event.*; ////////////////////// class EventThreadDemo //////////////////////// class EventThreadDemo { public static void main(String[] args) { LaunchAFrame laf1 = new LaunchAFrame(); //(A) LaunchAFrame laf2 = new LaunchAFrame(); //(B) laf1.start(); //(C) laf2.start(); //(D) } } //////////////////////// class LaunchFrame ///////////////////////// class LaunchAFrame extends Thread { //(E) public LaunchAFrame() {} public void run() { //(F) MyTools.printThreadInfo( //(G) "Just before creating Frame object:"); JFrame frame = new JFrame("EventThreadsDemo 2"); //(H) MyTools.printThreadInfo( "Just after creating Frame object:"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); JTextArea textArea = new JTextArea(); textArea.setLineWrap(true); textArea.setWrapStyleWord(true); textArea.getDocument().addDocumentListener( new MyDocumentListener()); MyTools.printThreadInfo( //(I) "Just after registering document listener:"); JScrollPane areaScrollPane = new JScrollPane(textArea); MyTools.printThreadInfo( //(J) "Just after creating the scroll pane:"); areaScrollPane.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); areaScrollPane.setPreferredSize(new Dimension(250, 250)); areaScrollPane.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Plain Text"), BorderFactory.createEmptyBorder(5,5,5,5)), areaScrollPane.getBorder())); frame.getContentPane().add( areaScrollPane, BorderLayout.CENTER); MyTools.printThreadInfo("Just before calling pack:"); frame.pack(); frame.setLocation(300, 300); frame.setVisible(true); MyTools.printThreadInfo("Just after calling setVisible:"); //(K) } } /////////////////////////// class MyTools /////////////////////////// class MyTools { //(L) public static void printThreadInfo (String s) { //(M) System.out.printIn(s); // Thread.currentThread().getThreadGroup().list(); // System.out.printIn( // "Number of threads in the current thread group:" // + Thread.currentThread().getThreadGroup().activeCount() ); System.out.printIn("The current thread is:" + Thread.currentThread()); } public static void keepBusy(int howLong) { //(N) long curr = System.currentTimeMillis(); while (System.currentTimeMillis() < curr + howLong) ; } } ////////////////////// class MyDocumentListener ///////////////////// class MyDocumentListener implements DocumentListener { public void insertUpdate(final DocumentEvent e) { String str = null; Document doc = e.getDocument(); int lengthText = doc.getLength(); try { str = doc.getText(lengthText-1, 1); } catch(BadLocationException badloc) { badloc.printStackTrace(); } MyTools.printThreadInfo("From iniside the listener:"); //(0) MyTools.keepBusy(500); System.out.print(str); } public void removeUpdate(DocumentEvent e) { } public void changedUpdate(DocumentEvent e) { } }

As mentioned already, this program creates two separate top-level windowsin two separate threads. By executing the class and examining the information printed out in the terminal window, it iseasy to see that the event processing for both the top-level windows is carriedout in the same Event Dispatch Thread. The program produces the following output before a user starts interacting with either of the top-level windows by entering text into them. The output corresponds to the two threads launched by main, Thread-0 and Thread-1. Each thread executes concurrently its own version of the code in the run method of the LauchAFrame class. Note the timeslicing in action by seeing how the output switches between the two threads as the two top-level GUI's are being constructed:

     Just before creating Frame object:         The current thread is: Thread[Thread-0,5,main]     Just before creating Frame object:         The current thread is: Thread[Thread-1,5,main]     Just before creating Frame object:         The current thread is: Thread[Thread-1,5,main]     Just before creating Frame object:         The current thread is: Thread[Thread-0,5,main]     Just before creating Frame object:         The current thread is: Thread[Thread-0,5,main]     ....     .... 

But after you start entering text in the text areas of the two top-level GUI windows, you'll see the same Event Dispatch thread executing the two instances of the MyDocumentListener class. Suppose you enter "hello" in one window and "jello" in the other, you'll see the following output

     From iniside the listener:         The current thread is: Thread[AWT-EventQueue-0,6,main]     h     From iniside the listener:         The current thread is: Thread[AWT-EventQueue-0,6,main]     e     ....     From iniside the listener:         The current thread is: Thread[AWT-EventQueue-0,6,main]     j     From iniside the listener:         The current thread is: Thread[AWT-EventQueue-0,6,main]     e     .... 

Notice that both instances of MyDocumentListener, one for each of the top-level GUI windows, are running in the same thread, the thread named AWT-EventQueue-0. (The commented out lines in the program will print out additional useful information about the running threads.)

All of the code we have shown so far for MyDocumentListener will get executed in the Event Dispatch Thread. We now wish to point out that nothing prevents a programmer from executing any of this code in some other thread, as illustrated by the following version of MyDocumentListener in which all of the event handling code is processed by a new thread each time the function insertUpdate is invoked because the user typed a new character in the text area. New threads are constructed in line (A) and launched in line (B) of the implementation shown below.

 
class MyDocumentListener implements DocumentListener { public void insertUpdate(final DocumentEvent e) { new Thread() { //(A) public void run() { String str = null; Document doc = e.getDocument(); int lengthText = doc.getLength(); try { str = doc.getText(lengthText - 1, 1); } catch(BadLocationException badloc) { badloc.printStackTrace(); } MyTools.printThreadInfo( "From iniside the listener:"); MyTools.keepBusy(500); System.out.print(str); } }.start(); //(B) } public void removeUpdate(DocumentEvent e) { } public void changedUpdate(DocumentEvent e) { } }

Since it is possible to run event handling code in different threads, as the above example illustrates, that raises a very important question: What if this code needs to change the state of some component of the GUI? Which thread should be responsible for that? The problem is that if we allow an arbitrary thread to change the state of the GUI, we could produce situations where a component, such as a JTextArea, shows information that is partially old and partially new.

To prevent such synchronization problems, it is recommended by the developers of Java that all methods that have the potential of altering the state of the GUI be placed in the Event Dispatch Thread. A GUI state altering method that is automatically placed in the EventDispatch Thread is called thread safe. Most Swing methods are not thread safe. In fact, as of now, there are only two methods that are thread safe-repaint and revalidate-as these are automatically inserted into the Event Dispatch Thread no matter from what thread they are invoked. So, naturally, with the rest of the Swing methods you have to be careful as to how you invoke them.

To make the above recommendation more specific: 'Once a Swing component is realized, all code that might affect or depend on the state of that component should be executed in the Event Dispatch Thread." A component is considered realized when it is either ready to be painted on the screen or has already been painted. A top-level container, such as a JFrame, JDialog, or a JApplet, is realized when any of the following methods is invoked on it: setVisible(true), show() or pack(). When a top-level container becomes realized, all the components it contains also become realized at the same time. A component also becomes automatically realized if it is added to a container that is already realized.

While the upside of all the event handling code being invoked through the Event Dispatch Thread is that one event handler will finish executing before the next one is taken up, the downside is that the code in each event handler must execute quickly so as not to degrade the GUI's overall response to user interaction. Basically, the GUI remains frozen to user interaction during the execution of event handler code. This makes it necessary for the code in an event handler to execute very quickly and that if more extensive processing (of, say, the information elicited from the user) is required, a separate thread be spawned.

Let's say we have spawned a separate thread for some heavy-duty computing needed inside a listener object. What if the result of this computing calls for changing the state of some GUI component?[14] Obviously, threads other than the Event Dispatch Thread would need to be able to place items in the event queue for altering the state of the GUI. The SwingUtilities class of the javax.swing package provides two static methods for this purpose:

     public static void invokeLater(java.lang.Runnable runnable)     public static void invokeAndWait(java.lang.Runnable runnable)             throws InterruptedException, InvocationTargetException 

The difference between the two is that while invokeLater executes asynchronously, invokeAndWait executes synchronously. That means that invokeLater will return immediately after it has placed its argument object in the event queue. On the other hand, invokeAndWait returns only after the argument object has been processed by the Event Dispatch Thread. In both cases, the Event Dispatch Thread will get to the new object placed in the event queue only after all the pending AWT/Swing events have been processed.

In the following example, the invokeLater call queues the Runnable object doHelloWorld in the Event Dispatch Thread in line (B). Subsequently, we want the message in line (C) to be printed out. The Event Dispatch Thread will first dispose of all the pending items in the event queue before executing the new item. So it is not unlikely that the print statement in line (C) will be executed before the print statement in line (A).

     Runnable doHelloWorld = new Runnable() {         public void run() {             System.out.println("Hello World from " +                 Thread.currentThread());                                 //(A)         }     };     SwingUtilities.invokeLater(doHelloWorld);                            //(B)     System.out.println("This might well be displayed "                 + "before the other message.");                          //(C) 

To compare with the behavior of invokeAndWait, in the example code shown below that uses invokeAndWait we have a guarantee that the application thread appThread will print out the message in line (D) before the message in line (E).

     final Runnable doHelloWorld = new Runnable() {         public void run() {             System.out.println("Hello World on " +                                 Thread.currentThread());                   //(D)         }     };     Thread appThread = new Thread() {         public void run() {             try {                 SwingUtilities.invokeAndWait(doHelloWorld);             }             catch (Exception e) {                 e.printStackTrace();             }             System.out.println("Finished on "                                 + Thread.currentThread());                 //(E)         }     };     appThread.start(); 

Both program fragments shown above are from the "Threads and Swing" section of [32].

For complex situations, when a separate thread launched in the event handler is suppose to alter the state of the GUI after finishing some time-consuming operation, it is best to use the SwingWorker class available from the java.sun.com site. This class optionally executes additional code in the Event Dispatch Thread for altering the state of the GUI at the conclusion of the time-consuming operation.

[13]Sometimes it is also referred to as the AWT thread.

[14]The following scenario should drive home this point more forcefully: The user enters some information in a text component. This information is retrieved in a listener method. But now this information must be used to fetch some other information from a remote site-a process that could take an indeterminate length of time. So you launch a separate thread for the fetching operationso as not to slow down the response of the Event Dispatch Thread to interactions by the user with the other components of the interface. After the other thread has fetched the needed information, you need to update the interface by altering the state of some GUI component. Since the other thread is not the Event Dispatch Thread, how does one do that?




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net