Blogs / Tech Blog

SwingUtilities.invokeAndWait… doesn’t.

One of the most misunderstood aspects of multithreaded Swing applications is care and feeding of SwingUtilities.invokeAndWait. Hans Muller and Kathy Walrath authored a nice article that includes an overview of when to use invokeLater or its slightly more risky sibling, invokeAndWait.

We often use worker threads to do some long-running process, so often run into two issues using SwingUtilities invokeLater/invokeAndWait, and have developed wrapper code to deal with it. One issue is executing code from both worker threads and the Event Dispatch Thread (EDT). invokeLater and invokeAndWait both throw exceptions if executed on EDT. Second, invokeAndWait isn’t guaranteed — interruption on the calling thread will resume execution before the job is finished. The remainder of this post shows the code we used to solve these issues.

Reducing repetitive code

We often write functions that are executed from the Event Dispatch Thread (EDT) and also from other threads. Since these methods will throw an exception if you’re already on the EDT, it makes sense for us to wrap them in a quick safety check so we avoid an extra three lines of code every place we want to use it. Thus, a first minor improvement:

public static runOnEDT(final Runnable r) throws InvocationTargetException {
	if ( SwingUtilities.isEventDispatchThread() )
		r.run();
	else
		SwingUtilities.invokeLater( r );
}

Note the use of invokeLater — that will be important to the next section. invokeAndWait wouldn’t work the same.

invokeAndWait doesn’t wait

The second gotcha with invokeAndWait is that it doesn’t always. Wait, that is. If a thread is waiting for a Runnable to execute on the EDT and that thread is interrupted, execution of the waiting thread will resume immediately, BEFORE FINISHING the Runnable. I had assumed this meant we failed to wait for the EDT, that the job was never executed and should I execute invokeAndWait again. This was the wrong interpretation. Turns out, all that happened was waiting on completion of the job was interrupted. The Runnable is still enqueued on the EDT and will still execute. Depending on how the timing works out, the EDT Runnable may even be executing at the same time. I just have no idea when it will finish. Very inconvenient.

invokeAndWait throws InterruptedException so you can learn of this situation and deal with it, but it’s not really something you can solve after the error has already occurred. So the following is our solution:

We create a FutureTask to wrap the provided Runnable. We can await completion of the FutureTask via FutureTask.get() which tells us when the provided Runnable has finished (successfully or thrown exceptions, or whatever). After the Runnable finishes, if there were any interruptions during execution, we re-interrupt Thread.currentThread() so the caller knows an interruption occurred and can choose to perform followup action.

public static void runAndBlockSilently(Runnable r) {
	final FutureTask ft = new FutureTask(r, null);
	boolean wasInterrupted = false;
	runSafely( ft );
	while (! ft.isDone() ) {
		try {
			ft.get();
		}
		catch ( InterruptedException e ) {
			wasInterrupted = true;
			// Continue ...
		}
		catch(ExecutionException exEx) {
			Throwable cause = exEx.getCause();
			logger.error( "Exception during BlockingRunnable: ", cause );
			if(cause instanceof RuntimeException)
				throw (RuntimeException) cause;
		}
	}

	if(wasInterrupted) {
		Thread.currentThread().interrupt();
	}
}

Happy Swinging.

-Carl

Other Blogs