外部 ProgressBar 不是 运行 与 Button Action Listener 并发

External ProgressBar is not running in concurrent with Button Action Listener

我用 Swing worker 创建了一个外部 class,运行 是进度条。这是代码,

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);
        }
    }
}

当我在 main class 中调用上面的代码时,上面的代码有效,如下所示。

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

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

但是,当我尝试在从数据库中获取大量行的操作按钮中调用相同内容时,大约需要 15-20 分钟。 progressbar 启动,一旦 db 进程启动,progressbar 被冻结,而 db statistics fetched.once 漫长的过程结束,progress bar 继续 运行 再次。

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. 
    });

你能帮我解决这个问题吗?

我刚刚在您在问题中发布的代码中添加了三行(并注释掉了另外两行)。我添加的行显示在下面的代码中,前面有注释。

import java.awt.EventQueue;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;

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, 10, 10));
        this.setTitle("Database Utility Execution");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(progressBar);
        this.add(label);

        /*Added next three lines.*/
        JButton b = new JButton("GO");
        b.addActionListener(e -> runCalc());
        add(b);

        pack();
//        this.setSize(100, 100);
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    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);
            }
        }
    }
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

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

当我点击 GO 按钮时,进度条开始填充。

代表您的应用程序的 MRE 可能如下所示:

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();
            }
        }
    }
}

此代码的问题在于 CheckStorage.DBVaultCheck()EDT 上启动了一个较长的进程。
Swing 是一个单线程库。所有绘画任务都在事件调度线程 (EDT) 中执行。 运行 EDT 上的长进程(如睡眠)使该线程保持忙碌,因此它不会做其他事情 比如更新gui。 gui 变得没有响应(冻结)。
假设 CheckStorage.DBVaultCheck() 更新 gui,您所要做的就是 运行 通过更改代码中的一行在不同线程上的长过程:

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

万一 CheckStorage.DBVaultCheck() 确实更新了图形用户界面,您必须采取措施确保这些更新发生在 EDT 上。 Swing gui 更新只能由 EDT 完成。