COMP 310

Cross-Thread Invocations onto the GUI Thread

    Current Home  Java Resources  Eclipse Resources

In general, Java will not let a GUI component be modified from a thread that is not the GUI thread.    This includes but is not limited to updating constructing/initializing the GUI elements themselves, updating text fields/areas, modifying drop-list items, etc.  Doing so may generate a "cross thread invocation" error at run-time. This means that all operations that affect the GUI must be transferred from the computation thread to the GUI thread before they are executed.   This means turning the operation into an event that can be queued into the GUI event handling queue.    This is not particularly difficult to accomplish but can be a bit tedious and on has to be very careful to be diligent about doing it.

It is important to realize that Java is not particularly good about catching and notifying you about cross-thread invocation problems.  You may have code that incorrectly modifies a GUI component on the wrong thread that will compile without warnings and run for ages without errors.     You may have code that seems to compile fine but show erratic behavior, incorrect renderings or other misbehaviors that vary from time to time and from machine to machine.    

Cross-thread invocation errors are very difficult to find and diagnose!  
Bottom line:  you must be very vigilant in your coding to always code against any problems. 

Suggestion:   Minimize the number of places in your code where a non-GUI thread, which usually emanate from the model side of your system, can perform operations that mutate the GUI.

The javax.swing.SwingUtilities class provides some methods that enable use to transfer the processing from a "worker" thread to the GUI event thread. 

SwingUtilities.invokeLater(Runnable doRun)  --  This static method creates an event that will run the given Runnable object and places it at the end of the GUI event queue to be executed when it reaches the front of the queue.   The current thread is NOT blocked and will immediately continue process, without waiting for the new event to complete on the GUI thread.   A call to invokeLater can be made from any thread, including the GUI event thread.

The Runnable interface (automatically imported via the java.lang package) defines a process that can be invoked:

public interface Runnable {
    public void run();
}

SwingUtilities.invokeAndWait(Runnable doRun)  --  This static creates an event that will run the given Runnable object and places it at the end of the GUI event queue to be executed when it reaches the front of the queue.   The current thread is IS blocked and will not continue to process until the new event to completes on the GUI thread.   A call to invokeLater CANNOT be made from the GUI event thread.   This call can throw either an InteruptedException or an InvocationTargetException, so it must be either be surrounded by a try-catch block or be in a method that also throws those exceptions.

To check if you are on the GUI event thread, use the static method SwingUtilities.isEventDispatchThread() method which returns true if the current thread is the GUI event thread.

 

Waiting for Input from the GUI

The problem with the above requirements is that GUI interactions fundamentally take place on a different thread, preventing the easy passage of values from the GUI thread back to the worker thread.   Passing values to the GUI thread is relatively straightforward because the anonymous inner class one tends to use for the Runnable being placed on the event queue will close over the variables being used by the worker thread.   Since we know when that Runnable is created, then we know what the values it closes over are.  The problem is that the worker thread does not know when its associated GUI event (the Runnable) will execute and thus does not know when any values determined by that GUI event become valid. 

A very classic scenario for this is when the worker thread needs to pop up a modal dialog box to the user to get some input in order to determine what needs to be done next.

What we need is for the worker thread to "block" (stop executing) while it is waiting for the results from the GUI event thread.   The easiest way to do this is to use a very simple implementation of what is more generally known as a "producer-consumer" model, where one entity produces values for another enitity to consume (process).  

What we need is a very simple "box" or 1-element queue where a value can be stored for later retrieval.   But more importantly, we need a box with a getter method for that value that will block the calling thread until a value is available.   That is, if a thread wishes to retrieve a value in the queue, the getter method will not return until that value is available in the queue, thus causing the calling thread to wait until the value is available.  This is called a "blocking queue".

Luckily, Java supplies the interfaces and implementations we need:

java.util.concurrent.BlockingQueue<T>  -- the interface that defines a blocking queue that holds objects of type T.   This defines a general, many-element queue.   We will be only be using a 1-element queue in our work here.

java.util.concurrent.ArrayBlockingQueue<T>  -- an implementation of BlockingQueue that uses an array of T as its back-end storage.   We can define a queue of a particular size with this implementation.   We will only need a queue of one element, i.e.

BlockingQueue<T> bq = new ArrayBlockingQueue<T>(1)  // instantiates a 1-element blocking queue

The methods we will need are:

BlockingQueue<T>.offer(T x)  -- puts the value x into the queue.   Returns false if there is no room left in the queue.

T BlockingQueue<T>.take()  --  returns the first value in the queue.   Does not return until there is a value to return.

The code might look like the following:

// Worker thread
BlockingQueue<Integer> bq = new ArrayBlockingQueue<Integer>(1); // Make 1-element blocking queue
SwingUtilities.invokeLater(new Runnable() {  // Put this Runnable on the GUI event thread
    public void run() {
        // This is on the GUI event thread
        int xGUI = JOptionPane.showConfirmDialog(null, "Choose Yes, No or Cancel");   // get choice from modal dialog
        bq.offer(xGUI);  // save the value in the blocking queue.
    }
});

int xWorker = bq.take();  // wait until a value is available in the blocking queue
// continue processing based on the value of xWorker

 

Be careful if you aren't sure if you are or are not already on the GUI thread!   Producer-consumer setups can deadlock if both sides are on the same thread!

For more information please see GUI Thread Deadlocking in Producer-Consumer or Data Synchronization Scenarios

 

For more information, please see the Comp310 Java Resources page on Threads.


Additional reading:

 


© 2017 by Stephen Wong