java swingworker_java – SwingWorker,done()在process()调用完成之前执行

简短的回答:

这是因为publish()不直接调度进程,它设置一个计时器,它将在DELAY之后触发EDT中的进程()块的调度。所以当工作人员被取消时,仍然有一个定时器等待安排一个进程()与最后一个发布的数据。使用定时器的原因是实现优化,其中可以使用多个发布的组合数据来执行单个进程。

长篇大论

让我们看看publish()和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状态被设置为CANCELED,线程被中断,并且调用了done(),但是这不是SwingWorker完成的,而是在SwingWorker构造函数中实例化变量时指定的将来的done():

future = new FutureTask(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);

}

}

如果我们在EDT这是我们的案例,那么直接调用SwingWorkers的done()。在这一点上,SwingWorker应该停止,不应该再调用publish(),这样可以很容易地进行下列修改:

while(!isCancelled()) {

textArea.append("Calling publish\n");

publish("Writing...\n");

}

但是我们仍然从process()获取一个“写入…”消息。所以我们来看看process()被调用。发布(…)的源代码是

protected final void publish(V... chunks) {

synchronized (this) {

if (doProcess == null) {

doProcess = new AccumulativeRunnable() {

@Override

public void run(List args) {

process(args); //

}

@Override

protected void submit() {

doSubmit.add(this); //

}

};

}

}

doProcess.add(chunks); //

}

我们看到Runnable doProcess的run()是最终调用进程(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();

}

Collections.addAll(arguments, args); //

if (!isSubmitted) { //This is what will make that for multiple publishes only one process is executed

submit(); //

}

}

那么publish()实际上是将块添加到一些内部的ArrayList参数中,并调用submit()。我们刚刚看到,只是调用doSubmit.add(this),这是一个非常相同的add方法,因为doProcess和doSubmit都会扩展AccumulativeRunnable V,但是这个时候大概V是Runnable而不是String,就像在doProcess中一样。所以一个块是调用process(args)的runnable。但是,submit()调用是一个完全不同的方法,在doSubmit类中定义:

private static class DoSubmitAccumulativeRunnable

extends AccumulativeRunnable implements ActionListener {

private final static int DELAY = (int) (1000 / 30);

@Override

protected void run(List 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(); //

}

}

它创建一个Timer,它在DELAY毫秒之后触发actionPerformed代码一次。一旦事件触发,代码将在EDT中排入队列,这将调用内部run(),最后调用run(flush())doProcess,从而执行进程(chunk),其中chunk是参数的刷新数据ArrayList的。我跳过了一些细节,“跑”的链接就是这样的:

> doSubmit.run()

> doSubmit.run(flush())//实际上是一个runnables循环,但只有一个(*)

> doProcess.run()

> doProcess.run(flush())

>进程(chunk)

(*)boolean isSubmited和flush()(它重置这个布尔值)使得它的发布更多的调用不会添加doSubmit.run(flush())中调用的doProcess runnable,但是它们的数据不被忽略。因此,在定时器使用期间调用的任何数量的发布执行单个进程。

总而言之,什么发布(“写作…”)是在DELAY之后调度EDT中的调用(块)。这解释了为什么即使我们取消了线程,没有更多的发布完成,还有一个进程执行出现,因为我们取消工作的那一刻(具有很高的概率)一个Timer将在done()之后调度进程()已经调度。

为什么这个计时器使用而不是在EDT中使用invokeLater(doProcess)调度进程()?要实现docs中解释的性能优化:

Because the process method is invoked asynchronously on the Event

Dispatch Thread multiple invocations to the publish method might occur

before the process method is executed. For performance purposes all

these invocations are coalesced into one invocation with concatenated

arguments.

For example:

06008

我们现在知道这是因为在DELAY间隔期间发生的所有发布都将它们的参数添加到我们看到参数的内部变量中,并且进程(chunk)将一次性执行所有数据。

这是一个BUG吗?解决方法吗?

很难说如果这是一个错误,处理后台线程已经发布的数据可能是有意义的,因为工作实际上已经完成,你可能有兴趣使用尽可能多的信息更新GUI, (如果这是进程()正在做的,例如)。然后,如果done()要求在done()之后处理所有数据和/或对process()的调用)创建数据/ GUI不一致,那么这可能没有意义。

有一个明显的解决方法,如果你不想在done()之后执行任何新的进程(),只需检查工作在进程方法中是否被取消!

@Override

protected void process(List chunks) {

if (isCancelled()) return;

String string = chunks.get(chunks.size() - 1);

textArea.append(string);

}

在最后一个进程()之后执行完成()是更棘手的,例如完成只能使用一个计时器,该计时器将在> DELAY之后调度实际的完成()工作。虽然我不能认为这是一个常见的情况,因为如果你取消了当我们知道我们实际上取消了所有未来的执行时,不应该错过一个进程()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值