【问题标题】:Java GUI - Progress bar doesn't update until the async task is finishedJava GUI - 在异步任务完成之前,进度条不会更新
【发布时间】:2021-05-11 21:31:35
【问题描述】:

我正在使用 CompletableFuture 运行长时间运行的操作。同时,我使用 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 线程上执行此操作,但无济于事。您所看到的基本上是我在这一点上尝试进行反复试验。

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

【问题讨论】:

  • 使用SwingWorker 的重点是您publish 将“更新”返回到事件调度队列,process 安全地进行这些更改。你也不应该改变组件的大小
  • SwingWorker 也有自己的进度支持,用于exampleexampleexampleexample

标签: java swing user-interface event-dispatch-thread jprogressbar


【解决方案1】:

停下来仔细看看Worker Threads and SwingWorker

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

您“可以”publish 更新进度并在process 方法中更新进度条,但是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;
            }
        }
    }
}

【讨论】:

  • 我想我没有提到这一点,所以我现在就提一下。异步任务没有任何中间结果。它有一个需要很长时间才能完成的方法调用。我正在尝试独立于任务更新进度条。
  • 当然,所以SwingWorker“可以”做到这一点,而且通常比SwingUtilities.invokeLater 更容易做到。我的“个人”直觉是定义一个工作流,允许您将进度观察者传递给任务,因此任务本身可以提供进度反馈,以便更好地指示已完成的工作量,但我们会需要有关正在发生的事情的更多信息。
  • 当你说“任务”时,你的意思是需要很长时间的方法调用吗?你需要什么信息?长时间运行的方法调用会进行网络扫描,它会扫描不同的子网以找出网络上的网络设备。我目前正在尝试确定是否有一种方法可以通过找出扫描的子网数量与子网总数来显示进度。
  • @FiddleBug 是的,asyncTask() 是什么。为了真实地显示进度,您需要知道要完成的工作量和剩余的工作量
  • @FiddleBug 从责任的角度来看。确定进度的数量不是“工人”的责任,而是任务。现在,要么任务将以直接方式执行此操作,例如 getSubNetsScannedgetTotalSubNets,要么通过某种观察者间接执行此操作。从那里,您需要确定用于更新 UI 的最佳工作流程。您可以在“直接”工作流中使用SwingWorker,因为您需要不断轮询任务的状态,或者为观察者轮询SwingUtiltiites#invokeLater(因为任务应该解耦)
【解决方案2】:

这将更新进度条,但由于 SwingWorker 不断循环,它会立即达到 100%。

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

在后一种情况下,你可以移除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 定义了一个可观察类 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();
        }
    }
}

【讨论】:

  • 为什么是第一个例子? SwingWorker 的重点是使用 publishprocess 工作流来允许您同步更新到 UI 安全。 SwingWorker 也有一个 progress 属性,它可以像第二个例子一样使用,所以,你重新发明轮子,两次 - 不是说你不能那样做,但为什么要麻烦:P
  • @第一个例子只是为了说明OP代码,即使更新了进度条,无论如何也不起作用。可能有点太挑剔了……这里根本不需要 SwingWorker 参与,因为它的进度必须取决于异步任务的进度,并且只是重复。
  • 没错,如果没有任务的细节,就很难确定行动方案,就个人而言,我很想将进度概念扩展到任务本身,这样它就可以反馈——但这可能会超出范围 - 感谢您的更新
猜你喜欢
  • 1970-01-01
  • 2016-07-16
  • 1970-01-01
  • 2020-07-10
  • 1970-01-01
  • 2017-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多