简短答案:
发生这种情况是因为publish()不直接进行调度
process,而是设置了一个计时器,该计时器将在EDT之后触发EDT中process()块的调度
DELAY。因此,当取消工作程序时,仍然有一个计时器等待使用上次发布的数据来调度process()。使用计时器的原因是为了实现优化,其中可以使用多个发布的组合数据执行单个过程。
长答案:
让我们看看publish()和cancel如何相互交互,为此,让我们深入研究一些源代码。
首先是简单的部分
cancel(true):
public final boolean cancel(boolean mayInterruptIfRunning) { return future.cancel(mayInterruptIfRunning);}此取消最终将调用以下代码:
boolean innerCancel(boolean mayInterruptIfRunning) { for (;;) { int s = getState(); if (ranOrCancelled(s)) return false; if (compareAndSetState(s, CANCELLED)) // <----- break; } if (mayInterruptIfRunning) { Thread r = runner; if (r != null) r.interrupt(); // <----- } releaseShared(0); done(); // <----- return true;}SwingWorker状态设置为
CANCELLED,线程被中断并被
done()调用,但这不是SwingWorker的完成,而是
futuredone(),它在SwingWorker构造函数中实例化变量时指定:
future = new FutureTask<T>(callable) { @Override protected void done() { doneEDT(); // <----- setState(Statevalue.DONE); }};和
doneEDT()代码是:
private void doneEDT() { Runnable doDone = new Runnable() { public void run() { done(); // <----- } }; if (SwingUtilities.isEventDispatchThread()) { doDone.run(); // <----- } else { doSubmit.add(doDone); }}done()如果我们在EDT中,则直接调用SwingWorkers
,这就是我们的情况。此时,SwingWorker应该停止,不再
publish()调用,这很容易通过以下修改进行演示:
while(!isCancelled()) { textarea.append("Calling publishn"); publish("Writing...n");}但是,我们仍然从process()收到“正在写…”消息。因此,让我们看看如何调用process()。的源代码
publish(...)是
protected final void publish(V... chunks) { synchronized (this) { if (doProcess == null) { doProcess = new AccumulativeRunnable<V>() { @Override public void run(List<V> args) { process(args); // <----- } @Override protected void submit() { doSubmit.add(this); // <----- } }; } } doProcess.add(chunks); // <-----}我们看到
run()Runnable
doProcess的最终是谁调用
process(args),但是这段代码只是
doProcess.add(chunks)不调用,周围也
doProcess.run()有一个
doSubmit。让我们来看看
doProcess.add(chunks)。
public final synchronized void add(T... args) { boolean isSubmitted = true; if (arguments == null) { isSubmitted = false; arguments = new ArrayList<T>(); } Collections.addAll(arguments, args); // <----- if (!isSubmitted) { //This is what will make that for multiple publishes only one process is executed submit(); // <----- }}因此,
publish()实际要做的是将这些块添加到一些内部ArrayList中
arguments并调用
submit()。刚才我们看到的是只提交调用
doSubmit.add(this),这是这同一个
add方法,因为这两个
doProcess和
doSubmit扩大
AccumulativeRunnable<V>,但是这一次
V是
Runnable不是
String在
doProcess。因此,可调用的块是可运行的
process(args)。但是,该
submit()调用是在类中定义的完全不同的方法
doSubmit:
private static class DoSubmitAccumulativeRunnable extends AccumulativeRunnable<Runnable> implements ActionListener { private final static int DELAY = (int) (1000 / 30); @Override protected void run(List<Runnable> args) { for (Runnable runnable : args) { runnable.run(); } } @Override protected void submit() { Timer timer = new Timer(DELAY, this); // <----- timer.setRepeats(false); timer.start(); } public void actionPerformed(ActionEvent event) { run(); // <----- }}它创建一个计时器,该计时器将
actionPerformed在数
DELAY毫秒后触发一次代码。一旦事件被触发的代码将在EDT被排队,这将调用内部
run(),其结束调用
run(flush())的
doProcess并且因此执行
process(chunk),其中块是的刷新的数据
arguments的ArrayList。我跳过了一些细节,“运行”调用链如下所示:
- doSubmit.run()
- doSubmit.run(flush())//实际上是一个可运行的循环,但只有一个(*)
- doProcess.run()
- doProcess.run(flush())
- 处理(块)
(*)布尔值
isSubmited和
flush()(将重置此布尔值)使得它的发布附加调用不会在doSubmit.run(flush())中添加要调用的doProcess可运行对象,但是不会忽略它们的数据。因此,对于一个计时器的生命周期内调用的任意数量的发布执行单个进程。
总而言之,在DELAY
之后
publish("Writing...")将调用调度到process(chunk)EDT中。这解释了为什么即使在我们取消线程并且不再进行任何发布之后,仍然会出现一个进程执行,因为在我们取消工作人员的那一刻(很有可能),已经安排了一个计划之后的计时器。
__
process()``done()
为什么使用此Timer而不是仅在带有EDT的EDT中调度process()
invokeLater(doProcess)?要实现文档中说明的性能优化:
由于该处理方法是在事件调度线程上异步调用的,因此在执行该处理方法之前,可能会对发布方法进行多次调用。为了性能起见,所有这些调用都被合并为一个带有级联参数的调用。例如:
publish("1"); publish("2", "3"); publish("4", "5", "6");might result in: process("1", "2", "3", "4", "5", "6")
现在我们知道这是可行的,因为在DELAY间隔内发生的所有发布都将它们添加
args到我们看到的内部变量中,
arguments并且
process(chunk)将一次性处理所有数据。
这是一个错误吗? 解决方法?
很难说这是否是一个错误,处理后台线程已发布的数据可能很有意义,因为该工作实际上已经完成,并且您可能有兴趣用尽可能多的信息来更新GUI
(
process()例如,如果这样做的话)。然后,如果
done()需要处理所有数据和/或在done()创建数据/
GUI不一致后调用process(),可能就没有意义了。
如果您不想在done()之后执行任何新的process(),则有一个明显的解决方法,只需检查该
process方法中的worker是否也被取消!
@Overrideprotected void process(List<String> chunks) { if (isCancelled()) return; String string = chunks.get(chunks.size() - 1); textarea.append(string);}要使done()在最后一个process()之后执行比较棘手,例如done可以只使用一个计时器,该计时器将在>
DELAY之后安排实际的done()工作。尽管我不认为这是常见的情况,因为如果您取消了它,那么当我们知道实际上正在取消所有将来的执行时,再错过一个process()也不重要。



