背景:
A well-known Swing best-practice requirement is that code that interacts with the Swing framework must also execute in EDT (Event Dispatch Thread).
因此,我更改了代码,以使基于 JFreeChart 的更新在 EDT 中运行。 然而,在“正常”线程上通常需要大约 7 分钟才能完成的完整图表显示任务,在 EDT 中运行时会变成几个小时的任务!
我做错了什么?我是否误解了 Swing 并发类(class)?我真的必须运行org.jfree.data.time.TimeSeries.addOrUpdate(date, double)
在 EDT 内?
请指教!
详细信息:
单击 Swing GUI 按钮,我的程序会触发一项耗时的任务。 基本上,它读取一个包含对值(日期、 double )的(大)文件,然后使用 JFreeChart 框架显示它们。
因为这是一项耗时的任务,在读取和显示数据时,JProgreessBar
在前台向用户显示进度状态,而图表在后台更新(用户仍然能够在进度条后面直观地看到每个图表更新)。
这工作得很好,直到我决定检查代码以更新我的图表数据并将其显示在 Swing EDT 中。然后,原本需要 7 分钟左右完成的任务,开始需要几个小时才能完成!
这是我正在使用的线程列表:
1) 由于该任务是由 Swing Button Listener 触发的,因此它在 EDT 中运行。 JProgressBar
也在同一个线程中运行;
2)同时显示JProgressBar
,第二个(“正常”)线程被创建并在后台执行。这是完成繁重工作的地方。
它包括 JProgressBar
的更新另一个线程上的状态(通过调用 JProgressBar.setvalue()
)以及我的 JFreeChart
的更新图表(通过调用 TimeSeries.addOrUpdate(date, double)
,它会自动更新 org.jfree.chart.ChartPanel
)。
在我的第二个(“正常”)线程中更新图表通常需要大约 7 分钟才能完成。没有任何明显的问题。
然而,知道大多数 Swing 对象方法都不是“线程安全的”并且 ChartPanel
只是一个用于显示 JFreeChart
的 Swing GUI 组件对象,我决定运行我的图表更新代码 TimeSeries.addOrUpdate(date, double)
位于美国东部时间 (EDT) 内。
仍在我的第二个“正常”线程中运行,我使用以下异步代码进行了测试:
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 并发类(class)?我真的必须在 EDT 内运行 TimeSeries.addOrUpdate(date, double) 吗?
请指教!
更新:
完整的代码太大,无法在此处显示,但您可以在下面找到代码快照。
也许,代码中唯一值得注意的是我使用了反射。这是因为我使用了一个通用的 ProgressBar 类,它在后台调用我作为参数发送的任何类(尽管在下面的快照中没有清楚地显示)。
//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);
最终你可以为它创建一个基类并使用多态性。
关于java - 在 EDT 中运行时显示 JFreeChart 点的性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60087106/