Java GUI - 在异步任务完成之前进度条不会更新

Java GUI - Progress bar doesn't update until the async task is finished

我正在使用 CompletableFuture 运行 一个长 运行ning 操作。同时,我使用 SwingWorker 以 5 为增量更新进度条。

            JProgressBar progressBar = new JProgressBar();
            progressBar.setMinimum(0);
            progressBar.setMaximum(100);
            progressBar.setValue(0);
            progressBar.setStringPainted(true);
            CompletableFuture<NetworkToolsTableModel> completableFuture = CompletableFuture.supplyAsync(() -> asyncTask());
            
            SwingWorker worker = new SwingWorker() {
                @Override
                protected Object doInBackground() throws Exception {
                    int val = 0;
                    while (!completableFuture.isDone()) {
                        if (val < 100) {
                            val += 5;
                            progressBar.setValue(val);
                        }
                        Rectangle bounds = progressBar.getBounds(null);
                        progressBar.paintImmediately(bounds);
                    }
                    return null;
                }
            };
            worker.execute();

在异步方法完成之前,进度条不会更新。我也尝试在 EDT 线程上执行此操作,但无济于事。你所看到的基本上是我在这一点上尝试做试错。

为什么进度条没有更新?我该如何解决这个问题?

这将更新进度条,但它会立即达到 100%,因为 SwingWorker 正在不断循环。

您应该通过轮询任务或(更好)使用观察者模式从异步任务中获取实际进度值。

后一种情况可以去掉swing worker,直接在观察者回调方法中更新进度条。

    SwingWorker worker = new SwingWorker() {
        @Override
        protected Object doInBackground() throws Exception {
            int val = 0;
            while (!completableFuture.isDone()) {
                if (val < 100) {
                    val += 5;
                    
                    final int prog=val;
                    SwingUtilities.invokeLater(()->{progressBar.setValue(prog); });
                }
            }
            return null;
        }
    };

这是一个完整的观察者模式示例,我使用 PropertyChangeSupport 定义了一个可观察的 class ALongTask,该任务将其进度值通知给所有已注册的观察者(即 PropertyChangeListener)。

package test;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;

public class TestProgressBar {


    public static void main(String[] args) {
        JProgressBar progressBar = new JProgressBar();
        progressBar.setMinimum(0);
        progressBar.setMaximum(100);
        progressBar.setValue(0);
        progressBar.setStringPainted(true);
        ALongTask asyncTask=new ALongTask();
        asyncTask.addPropertyChangeListener((e)-> { SwingUtilities.invokeLater(()->{progressBar.setValue((Integer) e.getNewValue()); }); });

        CompletableFuture.supplyAsync(asyncTask);

        JFrame frame=new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(progressBar);
        frame.pack();
        frame.setVisible(true);
    }
    
    public static class ALongTask implements Supplier<Object> {
        PropertyChangeSupport support=new PropertyChangeSupport(this);
        protected int progress;
        
        public void setProgress(int progress) {
            int old=this.progress;
            this.progress=progress;
            support.firePropertyChange("progress", old, progress);
        }
        
        public void addPropertyChangeListener(PropertyChangeListener l) {
            support.addPropertyChangeListener(l);
        }
        
        public void removePropertyChangeListener(PropertyChangeListener l) {
            support.removePropertyChangeListener(l);
        }

        @Override
        public Object get() {
            for (int i=0;i<20;i++) {
                setProgress(i*5);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    return null;
                }
            }
            setProgress(100);
            return new Object();
        }
    }
}

停下来仔细看看Worker Threads and SwingWorker

A SwingWorker 旨在允许您执行较长的 运行 任务,这可能会产生中间结果,并允许您将这些结果 publish 返回给事件调度线程安全处理(通过 process 方法)。

你“可以”publishprocess方法中更新进度条,但是SwingWorker已经提供了progress属性,你可以监控。查看 SwingWorker JavaDocs 中的一些现成示例!

运行 例子

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                ProgressPane progressPane = new ProgressPane();
                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(progressPane);
                frame.setSize(200, 200);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

//                progressPane.doWork();
            }
        });
    }

    public class ProgressPane extends JPanel {

        private JProgressBar progressBar;
        private JButton startButton;

        public ProgressPane() {

            setLayout(new GridBagLayout());
            progressBar = new JProgressBar();

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            add(progressBar, gbc);
            startButton = new JButton("Start");
            gbc.gridy = 1;
            add(startButton, gbc);

            startButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    startButton.setEnabled(false);
                    doWork();
                }
            });

        }

        public void doWork() {

            Worker worker = new Worker();
            worker.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if ("progress".equals(evt.getPropertyName())) {
                        progressBar.setValue((Integer) evt.getNewValue());
                    }
                }
            });

            worker.execute();

        }

        public class Worker extends SwingWorker<Object, Object> {

            @Override
            protected void done() {
                startButton.setEnabled(true);
            }

            @Override
            protected Object doInBackground() throws Exception {

                for (int index = 0; index < 1000; index++) {
                    int progress = Math.round(((float) index / 1000f) * 100f);
                    setProgress(progress);

                    Thread.sleep(10);
                }

                return null;
            }
        }
    }
}