在 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 并使用多态性。
背景:
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 并使用多态性。