栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

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

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

简短答案:

发生这种情况是因为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的完成,而是
future
done(),它在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()也不重要。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/507134.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号