java – JProgressBar won’t perform updates on UI used through SwingWorker

I’m using SwingWorker for a while now to handle updates on EDT and recently faced a situation where this approach won’t help to continue the task. here is my code and scenario.

My code contains several abstractions to handle the flow correctly

  1. AbstractOperationsManager – contains common code for each Operations manager
  2. Initializer – a concrete implementation of operation manager in the flow
  3. JProgressBarConsumer – a wrapper for abstracting routines related to UI from operations manager implementation
  4. OperationObserver – handling operation event updates
  5. OperationBroker – a mediator between OperationManager and OperationObserver
package org.app.core.api;

import java.util.Iterator;
import ...

public abstract class AbstractOperationManager extends SwingWorker<Void, Void> {

    private OperationBroker broker;
    private final Logger log = LoggerFactory.getLogger(AbstractOperationManager.class);
    private final List<UIConsumer> uiconsumer;
    private volatile boolean cancelled;

    public AbstractOperationManager() {
        super();
        this.uiconsumer = new LinkedList<>();
        this.cancelled = false;
    }

    public abstract void init(OperationBroker broker);

    public void addOperationsBroker(OperationBroker broker) {
        this.broker = broker;
    }

    public void addUIConsumer(UIConsumer consumer) {
        this.uiconsumer.add(consumer);
    }

    /**
     * customized method to inform observers of implicit cancellation of
     * operations because of either exception or for intentional purposes
     * 
     * @return boolean
     */
    public boolean isOperationCancelled() {
        return cancelled;
    }

    @Override
    protected void process(List<Void> chunks) {
        while (true) {
            OperationEvent latestE = this.broker.getObserver().getLatestEvent();

            if (latestE.getOperationType().equals(Operation.OPERATION_TYPE.TERMINATE)) {
                OperationTerminationEvent ote = (OperationTerminationEvent) latestE;
                if (ote.isExecThrown()) {
                    //log for exception
                    cancel(true);
                    cancelled = true;
                }
                break;
            } else if (latestE.getOperationType().equals(Operation.OPERATION_TYPE.LOG)) {
                //perform logging operations

            } else {
                SwingUtilities.invokeLater(()-> this.uiconsumer
                                .forEach(consumer-> consumer
                                                .consumeEvent(latestE)));
            }
        }
    }

    @Override
    protected Void doInBackground() throws Exception {
        publish();
        broker.operate();
        return null;
    }
}

The key execution point which results in updating the GUI is the iterator in uiconsumer. It’s an abstraction for a specific UI component to handle its flow and in this case, JProgressBar.

One reason for adding a bogus publish() call is to trigger the chunk processing and it is the broker who performs the operations and supplies the updates for the individual operations carried out.

package org.app.util;

import org.app.core.api.OperationEvent;
import org.app.core.api.OperationObserver;

public class SystemOperationObserver implements OperationObserver {

    private OperationEvent event;

    @Override
    public void notifyObserver(OperationEvent event) {
        try {
            eventQueue.put(event);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public int queuedCount() {
        return eventQueue.size();
    }

    @Override
    public OperationEvent getLatestEvent() {
        OperationEvent event = null;
        try {
            event = eventQueue.take();
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
        return event;
    }
}

OperationObserver is the event supplier which contains a LinkedBlockingQueue<T> for carrying out updates from several threads in OperationManager. Each and every operation holds access to this observer

package org.app.views.components;

import java.util.concurrent.TimeUnit;
import ...

public class JProgressBarConsumer implements UIConsumer {

    private JProgressBar progress;
    private Predicate<OperationEvent> predicate;

    public JProgressBarConsumer(JProgressBar progress) {
        this.progress = progress;
        this.progress.setStringPainted(true);
        this.progress.setValue(0);
    }

    private boolean filterEvent(OperationEvent event) {
        if (this.predicate != null) {
            return this.predicate.test(event);
        }
        return true;
    }

    @Override
    public void on(Predicate<OperationEvent> eventFilter) {
        this.predicate = eventFilter;
    }

    @Override
    public void consumeEvent(OperationEvent event) {
        boolean shouldContinue = filterEvent(event);
        if (shouldContinue) {
            int currentProgress = this.progress.getValue();
            int percentage = (int) event.getPercentage() + currentProgress;
            this.progress.setValue(percentage);     
            this.progress.repaint();
            try {
                TimeUnit.MILLISECONDS.sleep(60);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
package org.app.util;

import java.util.concurrent.CountDownLatch;
import ...

public class Initializer extends AbstractOperationManager {

    private CountDownLatch hook;

    public Initializer() {
        super();
        hook = new CountDownLatch(1);
    }

    @Override
    public void init(OperationBroker broker) {
        addOperationsBroker(broker);
    }

    public CountDownLatch getHook() {
        return hook;
    }

    @Override
    protected void done() {
        boolean isOpcancelled = this.isOperationCancelled();
        if (!isOpcancelled) {
            hook.countDown();
        }
    }
}

Scenario

The code should update the JProgressBar visually but what it really does is update only the last event progress (which the value is 100) and ignore the rest.

One reason could be that there should be a greater interval between UI events consumed by the JProgressBarConsumer TimeUnit.MILLISECONDS.sleep(60);

Another reason could be the code block the EDT between the operations because of LinkedBlockingQueue behavior until the Latch release the control

Questions

  • What could really be the cause of not updating JPogressBar?
  • The cancel(true) in AbstractOperationsManager isn’t working properly (It’s a problem with Future of course but please shed some light on that) and had to introduce a breaker manually. I did tons of research to understand the Future behavior and its cancel process and still no luck.

Leave a Comment