更快地通知 PropertyChangeListener

Notify PropertyChangeListener faster

所以我正在创建一个 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 wrapper实现的:

@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() 注册两个 'state' 事件和一个 'progress' 事件,将 ProgressBar 的进度从 0% 设置为 100%。

正在发生的事情 我认为正在发生的事情 用于澄清)在 JavaDocs 中有描述:

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

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

观察:我正在测试的处理过程需要 3~5 秒。

您的问题在这里:

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

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

相反,它应该从回调方法(例如 done() 方法或从 属性 更改侦听器在工作人员将其状态 属性 更改为 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.");
    }
}