Skip to main content

Swing and Roundabouts 1: Event DTs

Posted by evanx on May 30, 2006 at 7:30 AM PDT


swings4.jpg align=left vspace=8 hspace=16 border=0 />
Introduction

"People always said there were no monsters, no real ones... but there are."

In his blog " href="http://weblogs.java.net/blog/joconner/archive/2006/05/a_simple_framew.html">A
Simple Framework for Desktop Applications"
John O'Conner summarised a JavaOne presentation by Hans Muller and Scott Violet
vis-a-vis JSR 296.

John reports that this framework would give some guidance to Swing developers, to avoid
common bad practices. Such as Swing apps that only support English,
and Swing apps that are "a tangle of actionPerformed methods that block the
event dispatch thread (EDT)."

I commented there that the issue of EDT-blocking event handlers is a big one for me.
In my last project, most event handlers kicked off a string of "long tasks"
(namely, communicating to a server over a GSM network), and I got into
such a tangle with SwingWorkers upon SwingWorkers, that the code became increasingly
difficult to follow.

This blog presents how I eventually untangled that application.

alien6.jpg align=right vspace=8 hspace=16 border=0 />

Application

"All we know is that there is still is no contact with the colony, and that a xenomorph may be involved."

Imagine a client-server application, where the client application communicates
to the server application via the network.

Actually, this system is deployed on a remote planet managed by aliens.
Those same class of aliens as we met in the Aliens movies, if you can believe it.
The server room is installed in a secure complex on one of the moons.
(I can't be more specific because of the NDA the Company makes everyone sign, so...)

Let's consider the login dialog. The alien enters its username and password,
and then hits the "Login" button. This invokes our event handler to communicate
to the server as follows.

   Gui gui = new Gui(); // our GUI helper 
   Messenger messenger = new Messenger(); // our server comms helper

   public void actionPerformed(ActionEvent event) {
      if (event.getSource() == loginButton) {
         loginActionPerformed();
      }
   }
  
   protected void loginActionPerformed() {     
      try {
         LoginResponse loginResponse = messenger.sendLogin(getLoginBean());
         if (loginResponse.getErrorMessage() != null) {
            gui.showMessageDialog(loginResponse.getErrorMessage());
            if (loginResponse.isInvalidPassword()) passwordField.requestFocusInWindow();
            else usernameField.requestFocusInWindow();
            return;
         }
         if (loginResponse.isPasswordExpiring()) {
            String password = gui.showPasswordInputDialog("Enter new password");
            if (password != null) {
               ChangePasswordResponse response = messenger.sendChangePassword(loginResponse, password);
               if (response.getErrorMessage() != null) {
                  gui.showMessageDialog(response.getErrorMessage());
               } else {
                  gui.showMessageDialog("Password changed");
               }
            }
         }
         login(loginResponse);           
         gui.showMessageDialog("Welcome, " + loginResponse.getAlienName());
      } catch (CommsException ce) {
         gui.showMessageDialog(ce, "The frelling connection is down again.");
      } catch (Exception e) {
         gui.showMessageDialog(e, "System failure. Try a hard reboot.");
      }
   }

As you can see, we communicate the login information to the server (ie. username and password),
which might return with an error such as "Unknown Alien", "Access Denied" or "Invalid password."
Or we might get a comms exception, eg. caused by the high gamma ray activity on this
particular alien planet.

ktalkd.png align=left vspace=4 hspace=16 />
Finally, if the login is valid, but the password is expiring soon, then we ask the alien
to enter a new password.

Now the problem is that we are blocking the EDT in this method! In particular
when we communicate to the server. Because it might take 10 or 20 seconds to get
a response from the server on the moon.


EDT Blocking Problem

"In case you haven't been paying attention to current events, we just got our asses kicked, pal!"

To be 100% safe, we should not "manipulate" Swing components outside the EDT.
Because, as the Java Tutorial says,

"Swing event-handling and painting code executes in a single thread, called the
event-dispatching thread. This ensures that each event handler finishes executing
before the next one executes and that painting isn't interrupted by events.
To avoid the possibility of deadlock, you must take extreme care that Swing
components and models are created, modified, and queried only from the
event-dispatching thread."

That's fine because we typically manipulate our components (causing visual changes on the screen)
in our EDT event handlers. For example, when a button is pressed, an actionPerformed()
method is invoked by, and within, the EDT. And that's where our application actually does stuff,
ie. in response an alien pressing or clicking something. Because GUI applications are event-driven.

alienFight3.jpg align=right vspace=0 hspace=16 border=0 />
The problem is that we should not block the EDT for any length of time, eg. while waiting
for a response from the server. Because then our Swing app is "hung" eg. if the alien starts moving
windows around, they don't get repainted, and if it presses a window's "close" button,
or a "cancel" button, or clicks on a different tab, nothing frikkin happens. Quite frustrating. Now understand that these aliens get very irritated, very quickly, and can go flying off the handle, and we really don't want that.


Waking the SwingWorker

"This a lead works isn't it? All we gotta do is lure the beast into the mould!"

So the solution is to use a SwingWorker
thread, so that our long task can run as a separate thread outside the EDT.
Then we can start a SwingWorker thread, and let the EDT exit the
actionPerformed() method immediately, so that it can service other
events if necessary (eg. in the response to the user clicking on something).

We might restructure our earlier loginActionPerformed() method to use a SwingWorker
as follows.

   public void loginActionPerformed() {
      SwingWorker loginWorker = new SwingWorker() {
         LoginResponse loginResponse = null;
         Exception exception = null;
         public Object construct() {
            <b>// this is outside of the EDT, so no GUI stuff, just for our long task</b>
            try {
               loginResponse = messenger.sendLogin(getLoginBean());
            } catch (Exception e) {
               exception = e;
            }
            return null;
         }
         public void finished() { <b>// this is inside the EDT, for GUI stuff </b>
            if (exception != null) {
               String message = null;
               if (loginResponse != null) message = loginResponse.getErrorMessage();
               if (message == null) message = "A frak up has occurred";
               gui.showMessageDialog(exception, message);
            } else if (loginResponse == null) {
               gui.showMessageDialog(exception, "No response from server");
            } else if (loginResponse.getErrorMessage() != null) {
               gui.showMessageDialog(exception, loginResponse.getErrorMessage());
            } else {
               if (loginResponse.isPasswordExpiring()) {
                  String password = gui.showPasswordInputDialog("Enter new password");
                  if (password != null) {
                     ChangePasswordResponse response = messenger.sendChangePassword(loginResponse, password);
                     ... <b>// oh frell, a long task, and we are in the EDT here...</b>      
                     ... <b>// so this is so not gonna work</b>
                  }  
               }         
               gui.showMessageDialog("Welcome, " + loginResponse.getAlienName());              
            }
         }      
      };
      loginWorker.start(); // start the worker thread and free the        EDT
   }           

As you can see, we had to cajole our event handler quite significantly
to make it jump through a SwingWorker hoop. "Uh oh, this is crashing
my code-assist neural implants... I don't like it."


SwingWorker hive

"Seventeen days? Look man, I don't wanna rain on your parade, but we aint gonna last seventeen hours!"

testbed_protocol.png align=left vspace=4 hspace=16 />
The problem is that our "long task" is actually a chain of long tasks. First off, we communicate
the login info to the server. Clearly, this is a long task, which should run outside of the EDT.
When we get a response, we might popup a dialog to enter a new password. Uh oh, that should run in the EDT. Then we communicate to the server again, which is another
long task. Uh oh, that should run outside the EDT.

So we have to switch maybe a few times in and out of the EDT. SwingWorker is only
good for one switch out of the EDT (and then back again). "Frell it! You just can't win with these alien requirements!"

Nested SwingWorkers? Let's not even go there!


Reversing into a solution

"I say we take off and nuke the entire site from orbit. That's the only way to be sure."

Really, we wanna leave the code as it was before ie. as in the original loginActionPerformed()
implemention further above, without having to worry about blocking the EDT and SwingWorkers and all that.

But we know for sure that in order to make our event handler non-blocking, we will need
to run it via a SwingWorker thread. That's a given.
OK, so let's try the following.

   public void actionPerformed(ActionEvent event) {
      if (event.getSource() == loginButton) {      
         gui.startWorker(new Runnable() {
            public void run() {
               loginActionPerformed();
            }
         });
      }
   }     

We have introduced a startWorker(runnable) helper method, which we implement as follows.

    public void startWorker(final Runnable runnable) {
        SwingWorker swingWorker = new SwingWorker() {
            public Object construct() {
                try {
                   setWaitCursor(true);
                   runnable.run();
                } catch (Exception e) {
                   showExceptionDialog(e);
                } finally {
                   setWaitCursor(false);
                }
                return null;
            }
        };
        swingWorker.start();
    }

Note that we have no finished() method for GUI code, and our runnable event handler is run from the construct() method ie. outside the EDT, so it must be "EDT-safe." Let's put that aside for a second...

The above method takes care of showing the hour glass, as follows.

    public void setWaitCursor(boolean waitCursor) {
        if (!waitCursor) applicationFrame.setCursor(null);
        else applicationFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        glassPane.setVisible(waitCursor);
    }

Notice that we might also activate a glass pane to disable a particular panel while our task is in progress, eg. to force the user to wait for the task to complete.


Lose on the Swing, gain on the roundabout

"Nobody never gave me nothing. So I say, let's fight this beast ourselves! This is as good a place as any to take
our first steps to heaven. Do you wanna die on your feet, or on your frikkin knees?"

ksysguard.png align=left vspace=4 hspace=16 />
The trick is that our runnable GUI code must be made "EDT-agnostic." In particular, since we
use a GUI helper class, it's methods must be made EDT-agnostic.

Firstly, let's implement an EDT-agnostic invokeAndWait() method as follows.

   public void invokeAndWait(Runnable runnable) {
      if (!SwingUtilities.isEventDispatchThread()) {
         SwingUtilities.invokeAndWait(runnable);
      } else {
         runnable.run();
      }
   }

The above implementation of invokeAndWait() can be invoked safely
inside the EDT, or outside it, eg. in a SwingWorker's construct() method. As such, it is "EDT-safe."

Now we can "EDT-harden" methods invoked on Swing components in the loginActionPerformed()
further above, such as showConfirmDialog() in our Gui helper class, as follows.

    int dialogOptionValue;

    public int showConfirmDialog(final Object message, final String title, final int option) {
        invokeAndWait(new Runnable() {
            public void run() {
                dialogOptionValue = JOptionPane.showConfirmDialog((Component) applicationFrame, message, title, option);
            }
        });
        return dialogOptionValue;
    }

Similarly, we EDT-harden other methods in our GUI helper class, where these are commonly in our event handlers, eg. requestFocusInWindows() as below.

    
    public void requestFocusInWindow(final Component component) {
        invokeAndWait(new Runnable() {
            public void run() {
                component.requestFocusInWindow();
            }
        });
    } 

While this seems verbose and tedious, it is relatively quick to EDT-harden a method in our GUI
helper class, by cutting and pasting the invokeAndWait(Runnable) boiler-plate
code from another EDT-hardened method.

yast_profile-manager.png align=left vspace=4 hspace=16 />
At first, methods in our helper class are not necessarily EDT-agnostic, that is to
say, they do not use invokeAndWait(). When we encounter a need for that method
to be used in the construct() method of a SwingWorker, eg. via our
startWorker(runnable) helper, then we make that method
EDT-agnostic in our helper class via invokeAndWait(Runnable).

Similarly when we encounter a need for a method on a Swing component, we should
delegate that to our helper class, where we can make it EDT-safe.


Conclusion

"I dunno if I'm happy about this... I mean running around here in the dark with
that frikkin beast after us."

alienBox2.jpg align=right vspace=8 hspace=16 border=0 />
We should not block the EDT for any length of time, eg. while waiting
from a response from the server. Otherwise our Swing app appears to be "hung" to the user.

So we should use a SwingWorker thread, for our long tasks. However,
the SwingWorker is only good for one switch out of the EDT, and then back again.

This is a problem if our "long task" is actually a chain of long tasks, requiring
GUI invocations in between, eg. popping up a confirmation dialog before proceeding to the next long task.

If our event handler performs a long task, then we put it into a SwingWorker thread. But the trick is that we take care that any GUI manipulation is performed via an EDT-agnostic
helper class.

In our helper class, we use an invokeAndWait() method which will work
whether we are in the Swing EDT or not, by checking
SwingUtilities.isEventDispatchThread().

Our helper class may seem verbose and tedious, owing to the invokeAndWait(Runnable) boiler-plate code everywhere.
However, our application code is simpler and clearer. So it's a very good bargain.

We isolate the tedium in a single helper class, to free up the whole application for developers. It's a win-win, for us and the aliens.

Which means the aliens won't be blowing any developers out of the hatch, and visa versa, for now.

See the articles "Bean Curd"
and "Swing trashes Ajax" on my blog for further Swing reading :)

alienBox.jpg align=right vspace=8 hspace=16 border=0 />

alien14b.jpg

align=left vspace=8 hspace=16 border=0 />

-->