【问题标题】:Notify PropertyChangeListener faster更快地通知 PropertyChangeListener
【发布时间】:2018-10-05 19:51:22
【问题描述】:

所以我创建了一个 JProgressBar 来显示 CSV 操作的进度,其中每一行都被读取并检查是否在强制性 (NOT NULL) 列中没有空值。为此,我创建了一个 SwingWorker 任务,它处理将文件中的行数转换为最大进度值的 100%,并以正确的速率累加进度。

这就是 SwingWorker:

public static class Task extends SwingWorker<String, Object> {

    private int counter;
    private double rate;

    public Task(int max) {
        // Adds the PropertyChangeListener to the ProgressBar
        addPropertyChangeListener(
             ViewHandler.getExportDialog().getProgressBar());
        rate = (float)100/max;
        setProgress(0);
        counter = 0;
    }

    /** Increments the progress in 1 times the rate based on maximum */
    public void step() {
        counter++;
        setProgress((int)Math.round(counter*rate));
    }

    @Override
    public String doInBackground() throws IOException {
        return null;
    }
    @Override
    public void done() {
      Toolkit.getDefaultToolkit().beep();
      System.out.println("Progress done.");
    }
}

我的PropertyChangeListener由 JProgressBar 包装器实现

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

然后,在我实际使用它的地方,我用我需要的处理覆盖doInBackground() 方法,在每次迭代时调用step()

    Task read = new Task(lines) {
        @Override
            public String doInBackground() throws IOException {
                while(content.hasNextValue()) {
                step();
                // Processing
            }
            return output.toString();
        }
   };
   read.execute();
   return read.get();

发生了什么:处理工作并成功,然后调用done(),然后propertyChange()注册两个“状态”事件和一个“进度”事件,设置ProgressBar 的进度从 0% 到 100%。

发生了什么 我认为发生了什么check Hovercraft's answer 澄清)在 JavaDocs 中有描述:

因为在事件调度线程上异步通知 PropertyChangeListeners,所以在调用任何 PropertyChangeListeners 之前可能会发生对 setProgress 方法的多次调用。出于性能目的,所有这些调用仅与最后一个调用参数合并为一个调用。

那么,毕竟,我的问题是:我做错了什么吗?如果没有,我有没有办法让事件调度线程在 onProgress() 发生时通知 PropertyChangeListeners,或者至少不时通知?

Obs.:我正在测试的处理需要 3~5 秒。

【问题讨论】:

    标签: java swing event-dispatch-thread jprogressbar propertychangelistener


    【解决方案1】:

    你的问题在这里:

    read.execute();
    return read.get();
    

    get() 是一个阻塞调用,因此在执行您的工作线程后立即从事件线程调用它会阻塞事件线程和您的 GUI

    相反,它应该从回调方法(例如 done() 方法)或在 worker 将其 state 属性更改为 SwingWorker.StateValue.DONE 后从属性更改侦听器调用。


    例如

    import java.awt.*;
    import java.awt.event.*;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.TimeUnit;
    
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public class TestSwingWorkerGui extends JPanel {
        private JProgressBar progressBar = new JProgressBar(0, 100);
        private Action myAction = new MyAction("Do It!");
    
        public TestSwingWorkerGui() {
            progressBar.setStringPainted(true); 
            add(progressBar);
            add(new JButton(myAction));
        }
    
        private class MyAction extends AbstractAction {
            public MyAction(String name) {
                super(name);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                myAction.setEnabled(false);
                Task read = new Task(30) {
                    @Override
                    public String doInBackground() throws Exception {
                        int counter = getCounter();
                        int max = getMax();
                        while (counter < max) {
                            counter = getCounter();
                            step();
                            TimeUnit.MILLISECONDS.sleep(200);
                        }
                        return "Worker is Done";
                    }
                };
                read.addPropertyChangeListener(new MyPropListener());
                read.execute();
            }
        }
    
        private class MyPropListener implements PropertyChangeListener {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                String name = evt.getPropertyName();
                if ("progress".equals(name)) {
                    progressBar.setIndeterminate(false);
                    progressBar.setValue((Integer) evt.getNewValue());
                } else if ("state".equals(name)) {
                    if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                        myAction.setEnabled(true);
                        @SuppressWarnings("unchecked")
                        SwingWorker<String, Void> worker = (SwingWorker<String, Void>) evt.getSource();
                        try {
                            String text = worker.get();
                            System.out.println("worker returns: " + text);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
        private static void createAndShowGui() {
            TestSwingWorkerGui mainPanel = new TestSwingWorkerGui();
    
            JFrame frame = new JFrame("GUI");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(mainPanel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> createAndShowGui());
        }
    }
    

    class Task extends SwingWorker<String, Void> {
    
        private int counter;
        // private double rate;
        private int max;
    
        public Task(int max) {
            // Adds the PropertyChangeListener to the ProgressBar
            // addPropertyChangeListener(gui);
            // !!rate = (float)100/max;
            this.max = max;
            setProgress(0);
            counter = 0;
        }
    
        /** Increments the progress in 1 times the rate based on maximum */
        public void step() {
            counter++;
            int progress = (100 * counter) / max;
            progress = Math.min(100, progress);
            setProgress(progress);
            // setProgress((int)Math.round(counter*rate));
        }
    
        public int getCounter() {
            return counter;
        }
    
        public int getMax() {
            return max;
        }
    
        @Override
        public String doInBackground() throws Exception {
            return null;
        }
    
        @Override
        public void done() {
          Toolkit.getDefaultToolkit().beep();
          System.out.println("Progress done.");
        }
    }
    

    【讨论】:

    • 我忘了提到我的 ProgressBar 包装器实现了 PropertyChangeListener,所以这就是我在听的原因。但我从错误的角度看待问题,删除了“read.get();”从“返回”实际上解决了我的问题!我没有意识到它阻塞了我的整个应用程序和 GUI。谢谢!
    • @MateusW.:不客气。我对属性更改位不好,但是您不应该让您的 GUI 类实现您的侦听器接口。保持物品分开和清洁。注意我上面的minimal reproducible example 示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-04
    相关资源
    最近更新 更多