javaswingswingworkerjprogressbar

External ProgressBar is not running in concurrent with Button Action Listener


I created one external class with Swing worker that runs the progress bar. This is the code,

public class ExtProgressMonitor extends JFrame {

private static final long serialVersionUID = 1L;
private static final String s = "Database Statistics Report is exectuing in the Background";
private JProgressBar progressBar = new JProgressBar(0, 100);
private JLabel label = new JLabel(s, JLabel.CENTER);

public ExtProgressMonitor() {
    this.setLayout(new GridLayout(0, 1));
    this.setTitle("Database Utility Execution");
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.add(progressBar);
    this.add(label);
    this.setSize(100, 100);
    this.setLocationRelativeTo(null);
    this.setVisible(true);
    pack();
}

public void runCalc() {
    progressBar.setIndeterminate(false);
    progressBar.setStringPainted(false);
    TwoWorker task = new TwoWorker();
    task.addPropertyChangeListener(new PropertyChangeListener() {

        @Override
        public void propertyChange(PropertyChangeEvent e) {
            if ("progress".equals(e.getPropertyName())) {
                progressBar.setIndeterminate(false);
                progressBar.setValue((Integer) e.getNewValue());
            }
        }
    });
    task.execute();
}

private class TwoWorker extends SwingWorker<Integer, Integer> {

    private static final int N = 500;
    private final DecimalFormat df = new DecimalFormat(s);
    Integer x = 1;

    @Override
    protected Integer doInBackground() throws Exception {
        if (!javax.swing.SwingUtilities.isEventDispatchThread()) {
            System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() + returned false.");
        }
        for (int i = 1; i <= N; i++) {
            x = x - (((x * x - 2) / (2 * x)));
            setProgress(i * (100 / N));
            publish(Integer.valueOf(x));
            Thread.sleep(1000); // simulate latency
        }
        return Integer.valueOf(x);
    }

    @Override
    protected void process(List<Integer> chunks) {
        for (Integer percent : chunks ) {
           progressBar.setValue(progressBar.getValue() + percent);
        }
    }
}

The above code works when I call it in main class like below .

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {

        @Override
        public void run() {
            ExtProgress t = new ExtProgress();
            t.runCalc();
        }
    });
}

However When I try to call the same in my Action button that fetches a lot of row from database, takes around 15-20 minutes. progressbar gets launched, once the db process starts, the progressbar is frozen, while the db statistics is fetched.once the long process is over, the progress bar continues to run again.

private void jButton1ActionPerformed(java.awt.event.ActionEvent e) throws Exception {                                         
         
    ExtProgressMonitor t = new ExtProgressMonitor();
    t.runCalc();
    CheckStorage.DBVaultCheck(Host, port, instance, workbook, Schema_Password, schema);
    //.. Rest of the process, check storage comes from another class. 
    });

Can you please help me fix this issue?


Solution

  • An MRE representing your application could look like the following:

    import java.awt.GridLayout;
    import java.text.DecimalFormat;
    import java.util.List;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JProgressBar;
    import javax.swing.SwingWorker;
    
    public class SwingMain {
    
        SwingMain() {
            JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLocationRelativeTo(null);
            frame.add(new TestPanel());
            frame.pack();
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            new SwingMain();
        }
    }
    
    class TestPanel extends JPanel {
    
        public TestPanel() {
    
            JButton btn = new JButton("Run long process");
            btn.addActionListener(evt -> runLongProcess());
            add(btn);
        }
    
        private void runLongProcess() {
            new ExtProgressMonitor().runCalc();
            CheckStorage.DBVaultCheck();
        }
    }
    
    //For a second frame it is recommended to use JDialog instead of JFRame
    class ExtProgressMonitor extends JFrame {
    
        private static final long serialVersionUID = 1L;
        private static final String s = "Database Statistics Report is exectuing in the Background";
        private final JProgressBar progressBar = new JProgressBar(0, 100);
        private final JLabel label = new JLabel(s, JLabel.CENTER);
    
        public ExtProgressMonitor() {
            this.setLayout(new GridLayout(0, 1));
            this.setTitle("Database Utility Execution");
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            this.add(progressBar);
            this.add(label);
            this.setSize(100, 100);
            this.setLocationRelativeTo(null);
            this.setVisible(true);
            pack();
        }
    
        public void runCalc() {
            progressBar.setIndeterminate(false);
            progressBar.setStringPainted(false);
            new TwoWorker().execute();
        }
    
        private class TwoWorker extends SwingWorker<Integer, Integer> {
    
            private static final int N = 500;
            private final DecimalFormat df = new DecimalFormat(s);
            Integer x = 1;
    
            @Override
            protected Integer doInBackground() throws Exception {
                for (int i = 1; i <= N; i++) {
                    x = x - (x * x - 2) / (2 * x);
                    publish(Integer.valueOf(x));
                    Thread.sleep(1000); // simulate latency
                }
                return Integer.valueOf(x);
            }
    
            @Override
            protected void process(List<Integer> chunks) {
                for (Integer percent : chunks ) {
                    progressBar.setValue(progressBar.getValue() + percent);
                }
            }
        }
    }
    
    class CheckStorage{
    
        private static final int  LIMIT = 1000;
        public static void DBVaultCheck() {
            int counter = 0;
            while(counter ++ < LIMIT)   { //simulate long process
                try {
                    Thread.sleep(1000);
                    System.out.println(counter);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    

    The problem with this code is that CheckStorage.DBVaultCheck() starts a long process on the EDT.
    Swing is a single Thread library. All painting tasks are executed in the Event Dispatcher Thread (EDT). Running long processes (such as sleep) on the EDT makes keeps this thread busy, so it does not do other things like updating the gui. The gui becomes unresponsive (freezes).
    Assuming CheckStorage.DBVaultCheck() does not update the gui, all you have to do is run the long process on a different thread by changing a single line in the code:

        private void runLongProcess() {
            new ExtProgressMonitor().runCalc();
            new Thread(()->CheckStorage.DBVaultCheck()).start();
        }
    

    In case CheckStorage.DBVaultCheck() does update the gui, you'l have to take measures to make sure that those updates occur on the EDT. Swing gui updates should only be done by the EDT.