在 EDT 中 运行 时显示 JFreeChart 点的性能问题

Performance issue displaying JFreeChart points, when running in the EDT

背景:

A well-known Swing best-practice requirement is that code that interacts with the Swing framework must also execute in EDT (Event Dispatch Thread).

因此,我更改了我的代码,以便在 EDT 中将基于 JFreeChart 的更新更新到 运行。 然而,在“正常”线程上通常需要大约 7 分钟才能完成的完整图表显示任务,在美国东部时间 运行ning 时变成了几个小时的任务!


我做错了什么?我是否误解了 Swing 并发课程?我真的必须在 EDT 中 运行 org.jfree.data.time.TimeSeries.addOrUpdate(date, double) 吗?

请指教!


详情:

单击一个 Swing GUI 按钮,我的程序触发了一个耗时的任务。 基本上,它读取一个包含对值(日期、双精度)的(大)文件,然后使用 JFreeChart 框架显示它们。

因为这是一个耗时的任务,在读取和显示数据时,JProgreessBar在前台向用户显示进度状态,同时在后台更新图表(用户仍然可以直观地看到每个图表更新,在进度条后面)。

这工作得很好,直到我决定检查代码以在 Swing EDT 中更新和显示我的图表数据。然后,一个通常需要7分钟左右才能完成的完整任务,开始需要几个小时才能完成!


这是我正在使用的线程列表:

1) 由于任务是由 Swing Button Listener 触发的,因此在美国东部时间 运行ning。 JProgressBar 也在同一个线程中 运行ning;

2) 在显示 JProgressBar 时,会在后台创建并执行第二个(“正常”)线程。这是完成繁重工作的地方。

它包括另一个线程上 JProgressBar 状态的更新(通过调用 JProgressBar.setvalue())和我的 JFreeChart 图表的更新(通过调用 TimeSeries.addOrUpdate(date, double),自动更新 org.jfree.chart.ChartPanel).

在我的第二个(“正常”)线程中更新图表通常需要大约 7 分钟才能完成。没有任何明显的问题。


然而,知道大多数 Swing 对象方法不是 "thread safe" 并且 ChartPanel 只是一个用于显示 JFreeChart 对象的 Swing GUI 组件,我决定 运行 我的图表更新代码 TimeSeries.addOrUpdate(date, double) 在美国东部时间里。

仍然 运行 在我的第二个“正常”线程中,我使用以下异步代码进行了测试:

javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        TimeSeries.addOrUpdate(date, double);
    }
});

但我意识到我的 JProgressBar 会在图表更新之前达到 100%。 我想这是预料之中的,因为显示图表数据比获取和处理数据要慢得多。

然后我尝试了以下同步代码:

try {
    javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
            TimeSeries.addOrUpdate(date, double);
        }
    });
} catch (InvocationTargetException | InterruptedException e) {
    e.printStackTrace();
}   

这就是我发现性能问题的地方:以前需要大约 7 分钟才能完成的完整任务现在需要几个小时才能完成!


所以,我的问题是:

我做错了什么?我是否误解了 Swing 并发课程?我真的必须 运行 TimeSeries.addOrUpdate(date, double) inside EDT?

请指教!


更新:
完整的代码太大,无法在此处显示,但您可以在下面找到代码快照。
也许,代码中唯一值得注意的是我使用了反射。这是因为我使用了一个通用的 ProgressBar Class,它在后台调用任何 class 我将其作为参数发送(尽管这在下面的快照中没有清楚显示)。

//BUTTON LISTENER (EDT)
public void actionPerformed(ActionEvent arg0) {
    new Process_offline_data();
}


public Process_offline_data() {
    //GET DATA
    String[][] data = get_data_from_file();

    //CREATE PROGRESS BAR
    int maximum_progressBar = data.length;
    JProgressBar jpb = init_jpb(maximum_progressBar);

    //JDIALOG MODAL WINDOW
    JDialog jdialog = create_jdialog_window(jpb);


    Object class_to_invoke_obj = (Object) new Show_data_on_chart();
    String main_method_str = "do_heavy_staff"; 

    Runnable r = new Runnable() {
        public void run() {             

            //REFLECTION
            Method method = null;
            try {
                method = class_to_invoke_obj.getClass().getDeclaredMethod(main_method_str, JProgressBar.class, String[][].class);
            } catch (NoSuchMethodException | SecurityException e1) {
                e1.printStackTrace();
                jdialog.dispose();      //UNBLOCKS MAIN THREAD
                return;
            }

            try {
                method.invoke(class_to_invoke_obj, jpb, data);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
                e1.printStackTrace();
                jdialog.dispose();      //UNBLOCKS MAIN THREAD
                return;
            }
            //----------------


            jdialog.dispose();      //UNBLOCKS MAIN THREAD
        }
    };
    new Thread(r).start();
    //----------------


    //THIS IS STILL EDT
    jdialog.setVisible(true);       //BLOCKS HERE UNTIL THE THREAD CALLS jdialog.dispose();
}



public class Show_data_on_chart {
    public void do_heavy_staff(JProgressBar jpb, String[][] data) {

        TimeSeries time_series = get_TimeSeries();      //JFreeChart datamodel

        int len = data.length;
        for (int i=0; i<len; i++) {
            jpb.setValue(i+1);

            Millisecond x_axys_millisecond = convert_str2date(data[i][0]);
            Double y_axys_double = convert_str2double(data[i][1]);

            try {
                javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        //AUTOMATICALLY UPDATES org.jfree.chart.ChartPanel
                        time_series.addOrUpdate(x_axys_millisecond, y_axys_double);
                   }
                });
            } catch (InvocationTargetException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

我就是这样解决更新图表的问题的。

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.XYSeries;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import org.jfree.chart.plot.XYPlot;
import java.lang.reflect.InvocationTargetException;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;

public class App extends ApplicationFrame {

    XYSeries sin = new XYSeries("Sin");

    public App(String applicationTitle, String chartTitle) {
        super(applicationTitle);
        JFreeChart xylineChart = ChartFactory.createXYLineChart(chartTitle, "X", "Y",                                                                                                 new XYSeriesCollection(sin),
            PlotOrientation.VERTICAL, false, true, false);

    ChartPanel chartPanel = new ChartPanel(xylineChart);
    chartPanel.setPreferredSize(new java.awt.Dimension(560, 367));
    final XYPlot plot = xylineChart.getXYPlot();

    XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false);
    plot.setRenderer(renderer);
    setContentPane(chartPanel);
}

public Runnable r = new Runnable() {
    double x, y;
    int i;

    public void run() {
        int steps = 69999;
        for (i = 0; i < steps; i++) {
            //sample plot data
            x = Math.PI * 2.0 * 10.0 / ((double) steps) * ((double) i);
            y = Math.sin(x);

            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        if ((i % 1000) == 0) {
                            //adding data and redrawing chart
                            sin.addOrUpdate(x, y);
                        } else {
                            //adding point without redrawing of the chart
                            sin.add(x, y, false);
                        }
                    }
                });
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //redrawing chart if all data loaded
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    sin.fireSeriesChanged();
                }
            });
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

public Runnable rupdate = new Runnable() {
    public void run() {
        while (true) {
            //redrawing chart
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        sin.fireSeriesChanged();
                    }
                });
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //waiting for next update
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};

public static void main(String[] args) {
    final App chart [] = new App[1];

    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                chart[0] = new App(null, null);
                chart[0].pack();
                RefineryUtilities.centerFrameOnScreen(chart[0]);
                chart[0].setVisible(true);
            }
        });
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    Thread job = new Thread(chart[0].r);
    job.start();
    Thread job2 = new Thread(chart[0].rupdate);
    job2.start();
}
}

以上代码包括两种解决方案。您可以使用其中一个或两者。可以在数据馈送期间更新图表。例如,每第 100 个点和最后一个点之后。最终你可以制作一段时间后更新图表的外部线程。我每次都使用 updateAndWait 而不是 updateLater.

在你的代码中不要使用那样的反射。你应该制作界面。例如:

public interface IHardWork {
    public void do_heavy_staff(JProgressBar jpb, String[][] data);
}

并在完成工作的每个对象上实施它:

public class Show_data_on_chart implements IHardWork {
    public void do_heavy_staff(JProgressBar jpb, String[][] data) {
        // TODO Auto-generated method stub
    }
}

然后使用它:

IHardWork hwObj = new Show_data_on_chart();
hwObj.do_heavy_staff(jpb, data);
hwObj = new OtherHWObj();
hwObj.do_heavy_staff(jpb, data);

最终你可以为它创建一个基础 class 并使用多态性。