【发布时间】:2020-05-22 00:50:48
【问题描述】:
背景:
众所周知的 Swing 最佳实践要求是: 与 Swing 框架交互也必须在 EDT 中执行(事件 调度线程)。
因此,我更改了我的代码,让基于 JFreeChart 的更新在 EDT 中运行。 但是,在“正常”线程上通常需要大约 7 分钟才能完成的完整图表显示任务,在 EDT 中运行时变成了几个小时的任务!
我做错了什么?我误解了 Swing 并发课程吗?我真的必须在 EDT 中运行 org.jfree.data.time.TimeSeries.addOrUpdate(date, double) 吗?
请指教!
详情:
单击 Swing GUI 按钮,我的程序会触发一项耗时的任务。 基本上,它会读取一个包含对值(日期、双精度)的(大)文件,然后使用 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 组件,我决定在 EDT 中运行我的图表更新代码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 并发课程吗?我真的需要在 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();
}
}
}
}
【问题讨论】:
-
您应该在 EDT 上调用更新,但也许您经常这样做。如果您有大量数据,请考虑减少重绘图表的频率。
-
@hal:感谢您的快速响应,即确认应在 EDT 上调用我的更新。但是,很难相信我经常重画...我的样本,包括 71571 点,在 EDT 上需要将近 5 小时(!)才能完成。只有一个简单的 JProgressBar 在 EDT 上“同时”运行。此外,使用“常规”线程,只需不到 7 分钟即可完成!
-
我是否正确理解,使用 invokeLater 应用程序需要适当的时间才能完成,并在切换到 invokeAndWait 后变慢?
-
是的,没错。
-
事实上,与“正常”线程相比,invokeLater 需要大约 1 分钟才能完成(即大约 8 分钟)。不过,与 invokeAndWait 的约 5 小时相比,还有很长的路要走。
标签: java multithreading swing jfreechart edt