A First Pass at a Solution

problems. This isnt a viable solution for two reasons. First, all of Swing would have to have been written as threadsafe code. And that would have been an extraordinarily difficult thing to do; Swing is an enormously complex library of intertwined classes. In addition, even if it were done correctly, the additional necessary synchronization may have imposed a substantial performance penalty. The second reason for not making Swing threadsafe is that doing so would have forced all the people writing any sort of GUI to write threadsafe code. In other words, if Swing were to use multiple threads, then the code that interacts with Swing would have to be threadsafe as well. This is, in the parlance of modern business, a dealbreaker. Theres simply no reason for most programmers to have to deal with the complexities of writing threadsafe code whenever they want to put a slider on the screen. This is worse than it appears. If, on average, there are four print requests waiting, then the average time to complete a print request is four times the average time it takes to actually print a document because the clients have to wait for other print requests to finish. In other words, my GUI isnt just blocked while my print request is being handled, its blocked while your print request is being handled as well. What would probably wind up happening is that the remote method invocation would time out. That is, the client application would freeze for a while, then report back that the request timed out. Thats pretty awful.

21.2.1 A First Pass at a Solution

Working around this problem isnt too hard™the action listener can simply launch a new thread that makes the request and reports back. This is what the following code snippet does: private class PrintFile implements ActionListener, Runnable { public void actionPerformedActionEvent event { new Threadthis.start ; } public void run { try { FileInputStream documentStream = new FileInputStream_fileChooser. getSelectedFile ; DocumentDescription documentDescription = new DocumentDescriptiondocumentStream; Printer printer = Printer Naming.lookupDEFAULT_PRINTER_NAME; printer.printDocumentdocumentDescription; } catch PrinterException printerException { SwingUtilities.invokeLaternew PrinterExceptionMessageprinterException; return; } catch Exception exception { SwingUtilities.invokeLaternew ExceptionMessageexception; return; } SwingUtilities.invokeLaternew SuccessMessage ; } } private class PrinterExceptionMessage implements Runnable { private PrinterException _printerException; public PrinterExceptionMessagePrinterException printerException { _printerException = printerException; } public void run { String errorMessage = Print failed after + _printerException.getNumberOfPagesPrinted + pages.; JOptionPane.showMessageDialogClientFrameTwo.this, errorMessage, Error in printing , JOptionPane.INFORMATION_MESSAGE; _messageBox.setTextException attempting to print + _fileChooser. getSelectedFile.getAbsolutePath + \n\t Error was: + _printerException.getHumanReadableErro rDescription ; } } private class ExceptionMessage implements Runnable { private Exception _exception; public ExceptionMessageException exception { _exception = exception; } public void run { JOptionPane.showMessageDialogClie ntFrameTwo.this, Print failed, Error in printing , JOptionPane.INFORMATION_MESSAGE; _messageBox.setTextException attempting to print + _fileChooser. getSelectedFile.getAbsolutePath + \n\t Error was: + _exception.toString ; _exception.printStackTrace ; } } private class SuccessMessage implements Runnable { public void run { JOptionPane.showMessageDialogClientFrameTwo.this, Success, Document has been printed , JOptionPane.INFORMATION_MESSAGE; _messageBox.setTextPrint Request succeeded.; } } } This code contains one interesting wrinkle: because Swing isnt threadsafe, we cant simply report back from another thread. Reporting back involves creating instances of JDialog we will tell the user what happened inside a dialog box and storing a permanent record of the results inside a JTextArea . Both of these operations can be done safely only from within the Swing thread. Fortunately, Swing has one threadsafe object: the Swing event queue. You can drop off events, objects that implement the Runnable interface, into the Swing event queue. The Swing thread will, as its processing button clicks and slider-drags, pick up the objects you drop off, and invoke their run method just as if they were ordinary events. That is, the events generated by code and dropped off in the event queue eventually occur within the Swing thread. But, they block all user-interface events and repaints while they are executed Hence, it becomes crucially important that these code-generated events happen quickly and dont cause the GUI to freeze. Keep in mind that even though the objects put into the Swing event queue implement Runnable , they are not assigned distinct threads. Instead they are processed serially by the Swing event thread. This takes a bit of getting used to programmers used to dealing with Java threads are used to assuming that each Runnable gets a distinct thread. In order to take advantage of the Swing event queue, we wrote two additional classes, PrinterExceptionMessage and ExceptionMessage , which encapsulate the code we use to alter the user interface. Our background thread simply drops instances of these classes off in Swings event queue using the static method invokeLater defined in the SwingUtilities class. This should look familiar, at least in outline. Its exactly what we discussed in Chapt er 12 in Sect ion 12.2.5 . In addition, its the same design pattern we used to handle print requests in BatchingPrinter in that same chapter. This is now a decent solution from the client side. The button behaves correctly, and the Swing thread is free to refresh the screen and handle additional user events. Moreover, if the print request fails, the user will get a dialog box telling her about it.

21.2.2 Better Solutions