背景:
众所周知的 Swing 最佳实践要求是与 Swing 框架交互的代码也必须在 EDT(事件调度线程)中执行。
因此,我更改了代码以使基于 JFreeChart 的更新在 EDT 中运行。但是,在“正常”线程上通常需要大约 7 分钟才能完成的完整图表显示任务,在 EDT 中运行时变成了几个小时的任务!
我究竟做错了什么?我误解了 Swing 并发课程吗?我真的必须org.jfree.data.time.TimeSeries.addOrUpdate(date, double)
在 EDT 内跑步吗?
请指教!
细节:
单击 Swing GUI 按钮,我的程序会触发一项耗时的任务。基本上,它读取带有对值(日期、双精度)的(大)文件,然后使用 JFreeChart 框架显示它们。
因为这是一项耗时的任务,在读取和显示数据时,aJProgreessBar
在前台向用户显示进度状态,而在后台更新图表(用户仍然可以直观地看到每个图表更新,在进度条后面)。
这工作得很好,直到我决定查看代码以更新我的图表数据并在 Swing EDT 中显示。然后,一个通常需要7分钟左右完成的任务,开始需要几个小时才能完成!
这是我正在使用的线程列表:
1) 由于任务是由 Swing Button Listener 触发的,因此它在 EDT 中运行。JProgressBar
也在同一个线程中运行;
2) 在显示 时JProgressBar
,会创建第二个(“正常”)线程并在后台执行。这是完成繁重工作的地方。
它包括JProgressBar
另一个线程上状态的更新(通过调用JProgressBar.setvalue()
)和我的JFreeChart
图表的更新(通过调用TimeSeries.addOrUpdate(date, double)
,它会自动更新 a 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 并发课程吗?我真的必须在 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();
}
}
}
}