Prevent Swing Gui Locking Up During a Background Task

Prevent Swing GUI locking up during a background task

The problem is, your long running task is blocking the Thread that keeps the GUI responsive.

What you will need to do is put the long running task on another thread.

Some common ways of doing this are using Timers or a SwingWorker.

The Java tutorials have lots of information regarding these things in their lesson in concurrency.

To make sure the first task finishes before the second, just put them both on the same thread. That way you won't have to worry about keeping two different threads timed correctly.

Here is a sample implementation of a SwingWorkerFor your case:

public class YourTaskSwingWorkerSwingWorker extends SwingWorker<List<Object>, Void> {
private List<Object> list
public YourClassSwingWorker(List<Object> theOriginalList){
list = theOriginalList;
}

@Override
public List<Object> doInBackground() {
// Do the first opperation on the list
// Do the second opperation on the list

return list;
}

@Override
public void done() {
// Update the GUI with the updated list.
}
}

To use this code, when the event to modify the list is fired, create a new SwingWorker and tell it to start.

GUI freezes with long running task in a SwingWorker Thread

sw.run();

You should be using:

sw.execute();

To start execution on another Thread.

Read the section from the Swing tutorial on Concurrency for more information and working examples.

Swing application problem

It sounds to me like you are doing some sort of slow IO or calculations that are causing your GUI to become unresponsive.

What you need to do is do the long running processes in another thread.

The standard way of doing that is with a SwingWorker class.

The Java tutorials have some great resources and tutorials on how to properly use the SwingWorker.

Here and here is a link to another question that I believe may be similar.

This behavior you are seeing is a result of your long running process blocking the thread that the GUI uses to repaint itself. Thus, while your task is performing, the GUI is unable to repaint your JFrame and it becomes unresponsive.

Swing Multithreading. My GUI is freezing

You might actually have to launch a new Thread, so the blocking operations do not affect your application GUI too much. However, operations within that update the GUI should be executed by the original Event Dispatch Thread.

As pointed in other answers, here the main offender seems to be the use of Thread.sleep(). Which when executed in the Event Dispatch Thread will cause the GUI to become irresponsive (won't accept your input or redraw until it has finished executing your event listener code). However that Thread.sleep() is acceptable if used in other Threads (which wont freeze your GUI).

How to do it

First: Launch your blocking handling code in a separate Thread.

public void actionPerformed(ActionEvent e) {
if ((e.getActionCommand()).equals("spam1")) {
new Thread(){
@Override
public void run() {
try {
Spambot.Start("data/firstfile.txt");
} catch (FileNotFoundException | InvocationTargetException |
AWTException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
// ... rest of conditions

}

Second, every individual GUI update between delays, should be done in the Event Dispatch Thread.

EventQueue.invokeAndWait(new Runnable(){
public void run() {
robot.keyPress(KeyEvent.VK_ALT);
};
});

As all the updates done are in robot.keyPress() invocations, a good choice might be to encapsulate and re-use in a method. Note that local variables and arguments that are used inside inner class should be defined as final (so they are made available outside the stackframe of the method)

private static void invokeRobotInAWT(final Integer ... ke) throws InvocationTargetException, InterruptedException {
EventQueue.invokeAndWait(new Runnable(){
public void run() {
for (int currentEvent : ke) {
robot.keyPress(currentEvent);
}
};
});
}

public static void Start(String path) throws AWTException, InterruptedException, FileNotFoundException, InvocationTargetException {
try (Scanner input = new Scanner(new FileReader(path));) {
Spambot keyboard = new Spambot();
Random rand = new Random();
invokeRobotInAWT(KeyEvent.VK_ALT);
Thread.sleep(150);
invokeRobotInAWT(KeyEvent.VK_TAB);
Thread.sleep(150);
invokeRobotInAWT(KeyEvent.VK_TAB, KeyEvent.VK_ALT);
Thread.sleep(500);
while (input.hasNextLine() && !stopped) {
// @@@ Would need extra threading here?
keyboard.type(input.nextLine());
Thread.sleep(rand.nextInt(1500)+1000);
invokeRobotInAWT(KeyEvent.VK_ENTER, KeyEvent.VK_ENTER);
}
} finally {
// input closed by try-with-resources
}
}

Edited: Oops. I got it wrong with SwingWorker. Might actually be adequate.

Note: There are helper components in Swing that might spare us from complex and error-prone thread handling. You might actually use a SwingWorker where your overridden doInBackground() method (in the worker thread) traverses the file, does the pauses, and sends the keystrokes (call publish(Integer)), to be processed by the EDT in the overridden process(List<Integer>) method.

Properly to loading a large Java Swing UI without freezing it

There is two possibilities to solve this problem.

  1. You split your UI initialization in small chunks and call each chunk using SwingUtilities.invokeLater. Each invokeLater triggers repainting, so your GUI can be refreshed. This should be the preferred way.
  2. When you have problems on splitting your appllication into small independent chunks, you can "misaply" the Foxtrot lib to provide synchron breaks for UI repainting

    public static void sleepNonBlocking(long millis) {
    Worker.post(new Job() {

    @Override
    public Object run() {
    try {
    Thread.sleep(millis);
    } catch (Exception e) {
    // nothing
    }
    return null;
    }
    });
    }

    You need to call this method on some places in your application to force repainitng.

Correct design to prevent blackscreen issue in JFrame

The wait loop time increases and the GUI main window turns black till computations are done.

Then you long running task is executing on the Event Dispatch Thread (EDT) which prevents the GUI from repainting itself. You need to execute the long running task in a separate Thread. A SwingWorker is a separate Thread which has an API that also allows you to execute code on the EDT as required, for example when the code finishes executing or when you have intermediate results.

Read the section from the Swing tutorial on Concurrency for more information. You can always search the forums for example of using a SwingWorker.

JFrame Will Not Close

I would suggest you to put this kind of processing in a separate thread.
As long as you keep it in the main body of your code, Swing components will not respond to any user interaction as long as the loop is running.

In this page you can find some good practices regarding asynchronous operations in Swing applications. I would like to highlight one of the topics:

Rule #2: do not run time-consuming operations on the event thread.

I'll keep repeating this: Swing uses a single thread for all GUI events. If your event handler is uploading a multi-megabyte file across the net, those events will be delayed until you're done. There is a balancing act involved with this rule: some operations (such as getting the size of a file) are so fast that they won't interrupt the user. However, that's not a reason to be cavalier, because some day your user will be getting the size of a file that resides on a fileserver that has an intermittent network connection.

This other page also shows an example of how to handle long-running tasks in a Swing application. You may also want to have a look at the SwingWorker class.

Manage GUI and EDT in a multi-task application

As suggested by others, the best way is to use SwingWorker.

SwingWorker properties are listenable and listeners are always called in the EDT, thus, you could do something like:

public class ArchivingWorker extends SwingWorker<Void, Void> {
JProgressBar progressBar = null;
// Other members here...
...

public ArchivingWorker(...) {
// Any specific initialization here (in EDT)
addPropertyChangeListener(new PropertyChangeListener() {
@Override void propertyChange(PropertyChangeEvent e) {
if ( "state".equals(e.getPropertyName())
&& e.getNewValue() == StateValue.STARTED) {
// Background thread has just started, show a progress dialog here
progressBar = new JProgressBar();
...
}
else if ("progress".equals(e.getPropertyName())) {
// Update progress bar here with e.getNewValue()
...
}
}
});
}

@Override protected Void doInBackground() {
// Archiving process here and update progress from time to time
setProgress(progress);

return null;
}

@Override protected void done() {
// Ensure that archiving process worked correctly (no exception)
try {
get();
} catch (Exception e) {
// Handle exception (user feedback or whatever)
} finally {
// Close progress dialog
...
}
}
}

Then you can use ArchivingWorker as you need it:

ArchivngWorker worker = new ArchivingWorker(...);
worker.execute();


Related Topics



Leave a reply



Submit