我继续阅读并研究练习册中的并发性,我无法理解9.3.2节中的例子。
本章关于GUI应用程序开发。
在文中,作者说如果事件处理程序是长时间运行的任务 - 你可以在分离的线程中运行它来提高应用程序的响应能力。
并且作者在书中提供了以下代码:
BackgroundTask:
abstract class BackgroundTask<V> implements Runnable, Future<V> {
private final FutureTask<V> computation = new Computation();
private class Computation extends FutureTask<V> {
public Computation() {
super(new Callable<V>() {
public V call() throws Exception {
return BackgroundTask.this.compute();
}
});
}
protected final void done() {
GuiExecutor.instance().execute(new Runnable() {
public void run() {
V value = null;
Throwable thrown = null;
boolean cancelled = false;
try {
value = get();
} catch (ExecutionException e) {
thrown = e.getCause();
} catch (CancellationException e) {
cancelled = true;
} catch (InterruptedException consumed) {
} finally {
onCompletion(value, thrown, cancelled);
}
}
;
});
}
}
protected void setProgress(final int current, final int max) {
GuiExecutor.instance().execute(new Runnable() {
public void run() {
onProgress(current, max);
}
});
}
// Called in the background thread
protected abstract V compute() throws Exception;
// Called in the event thread
protected void onCompletion(V result, Throwable exception,
boolean cancelled) {
}
protected void onProgress(int current, int max) {
}
// Other Future methods forwarded to computation
}
和 runInBackground 方法:
public void runInBackground(final Runnable task) {
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
class CancelListener implements ActionListener {
BackgroundTask<?> task;
public void actionPerformed(ActionEvent event) {
if (task != null)
task.cancel(true);
}
}
final CancelListener listener = new CancelListener();
listener.task = new BackgroundTask<Void>() {
public Void compute() {
while (moreWork() && !isCancelled())
doSomeWork();
return null;
}
public void onCompletion(boolean cancelled, String s,
Throwable exception) {
cancelButton.removeActionListener(listener);
label.setText("done");
}
};
cancelButton.addActionListener(listener);
backgroundExec.execute(task);
}
});
}
我无法理解这段代码的逻辑。根据代码,我看到runInBackground
方法将侦听器添加到 startButton ,该侦听器在分离的(非Swing)线程中运行任务(以runInBackground
方法的参数传递)。
但这种方法的其他代码对我来说不清楚。根据书中的文字,我们应该有可能从Swing线程中断这个任务。但是在方法文本中,我们向 cancelButton 添加了额外的监听器,这使得与停止作为runInBackground
方法参数传递的任务无关的工作。
请澄清这件事。
答案 0 :(得分:2)
Swing基本上是单线程的。如果长时间运行的任务在Event Dispatch Thread (EDT)上排队,它将阻塞直到完成并阻止进一步的事件处理。如果EDT被阻止,这将使GUI看起来“冻结”(至少在队列处理恢复之前)。
为避免这种情况,将非GUI工作放到其他线程上是一种很好的做法。通常,这些线程的创建由GUI事件触发,并且线程通过回调发出完成信号,因此可以根据需要更新GUI。
在此示例中,CancelButton
上的按钮按下事件来自EDT。但是,按下该按钮的事件处理程序必须保留对BackgroundTask
的引用,以便它可以调用cancel(true)
方法。因此,当用户点击startButton
时,会出现以下序列:
ActionListner
用于startButton
由用户按下按钮触发。
BackgroundTask
已创建。ActionLister
的新BackgroundTask
附加到cancelButton
BackgroundTask
已启动。如果用户点击取消按钮,则附加的ActionLister
会在cancel()
上致电BackgroundTask
。
否则,BackgroundTask
完成后,会删除CancelListener
,因为取消已完成的任务将毫无意义。
有关EDT如何运作的更多信息,您可以查看以下问题:Java Event-Dispatching Thread explanation
编辑 正如评论中所指出的,Swing组件通常不是线程安全的,它是recommended they only be modified from the EDT。
乍一看,似乎onCompletion()
BackgroundTask
方法正在修改工作线程中的cancelButton
和label
而不是EDT。
但是,GuiExecutor
提供了execute()
方法,可确保传入的Runnable
在EDT上运行:
public void execute(Runnable r) {
if (SwingUtilities.isEventDispatchThread())
r.run();
else
SwingUtilities.invokeLater(r);
}
BackgroundTask
使用execute()
方法调用其onCompletion()
方法。因此,对cancelButton
和label
的修改正在从EDT运行。
由于runInBackground
它也在操纵Swing组件,所以它也应该从EDT调用。
答案 1 :(得分:1)
这本书有errata列表
在代码清单9.8中,应该是backgroundExec.execute的参数 listener.task而不仅仅是task,以及第一行和最后一行 列表应该删除。
因此我的担心是正确的。