Skip to content

Why does my GUI freeze?

discorddioxin edited this page May 28, 2021 · 15 revisions

Contents


Why does my GUI freeze?

When an event happens, like a button click event, a long-running task may need to be performed

  • Processing large data sets - large collections or databases
  • Performing operations that block - typically IO operations
  • IO operations - reading files or loading network data

If the long-running task is executed on the UI thread, your program may freeze.

Event listener methods run on a UI Thread

Your events will be placed in a queue. If you block up the queue, other events such as rendering will have to wait.


Diagnosing

The following methods can determine if your code is ran on a UI thread

public void actionPerformed(ActionEvent e) {
    System.out.println(SwingUtilities.isEventDispatchThread()); // true
}
public void handle(ActionEvent event) {
    System.out.println(Platform.isFxApplicationThread()); // true
}

Solution

Introduce a new thread to handle the long-running task

There are many ways to create threads. See Thread Objects and High Level Concurrency Objects (more specifically Executors)

// could be Swing methods like actionPerformed
// could be JavaFX methods handle
// could be your own custom method - see Diagnosing to find out

public void yourMethod() {
    Thread thread = new Thread(() -> {
        // long-running task
    });

    thread.start();
}

Make sure any UI-related code is posted back to the UI thread

If your long-running task updates the UI, make sure those updates are on the UI thread.

  • Example Code
public void yourMethod() {
    Thread thread = new Thread(() -> {
        for(long l = 0; l < Long.MAX_VALUE; l++)
            if(l % 31 == 0)
                // update UI to display value of l
    });

    thread.start();
}
  • Swing Solution
for(long l = 0; l < Long.MAX_VALUE; l++) {
    if(l % 31 == 0) {
        long displayValue = l; // variable must be effectively final
            
        SwingUtilities.invokeLater(() -> {
            // update UI to display value
        });
    }
}

~ invokeLater will put your code at the end of the queue

~ invokeAndWait will do the same, but will block the current thread until the queued code is executed

  • JavaFX Solution
for(long l = 0; l < Long.MAX_VALUE; l++) {
    if(l % 31 == 0) {
        long displayValue = l; // variable must be effectively final
            
        Platform.runLater(() -> {
            // update UI to display value
        });
    }
}

~ runLater will put your code at the end of the queue

~ There is no runAndWait - you must implement this yourself

This still won't work for high-throughput logic!

Introducing a new thread helps, but a new problem arises: we are putting too many events into the EDT's queue

This will only work if you don't update the UI frequently


Workers

If your long-running code updates the UI frequently, you should use a worker

Swing: SwingWorker

public void actionPerformed(ActionEvent e) {
    
    SwingWorker<Void, Long> worker = new SwingWorker() {

        protected Void doInBackground() {
            for(long l = 0; l < Long.MAX_VALUE; l++)
                if(l % 31 == 0)
                    publish(l);
            
            return null;
        }

        protected void process(List<Long> items) {
             Long latest = items.get(items.size() - 1);
             // display latest to text area
        }
    }

}

WARNING: If using primitives, autoboxing could cause performance overhead. Use the system which best suits your program's needs.

JavaFX: Worker

  • Task

TODO

  • Service

TODO


Code Demos

Swing

JavaFX

Clone this wiki locally