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

Java SwingWorker,done()在process()调用完成之前执行,java,swing,swingworker,Java,Swing,Swingworker,我和s一起工作了一段时间,结果却有了一种奇怪的行为,至少对我来说是这样。我清楚地理解,由于性能原因,对方法的多个调用在一个调用中合并。这对我来说非常有意义,我怀疑SwingWorker保持某种队列来处理所有这些调用 根据和API,当SwingWorker结束其执行时,要么正常完成,要么从外部取消工作线程,然后调用该方法。到目前为止还不错 但我有一个例子(类似于教程中所示),在执行process()method调用之后,会执行done()方法调用。由于这两个方法都是在中执行的,我希望在所有proc

我和s一起工作了一段时间,结果却有了一种奇怪的行为,至少对我来说是这样。我清楚地理解,由于性能原因,对方法的多个调用在一个调用中合并。这对我来说非常有意义,我怀疑SwingWorker保持某种队列来处理所有这些调用

根据和API,当SwingWorker结束其执行时,要么正常完成,要么从外部取消工作线程,然后调用该方法。到目前为止还不错

但我有一个例子(类似于教程中所示),在执行
process()
method调用之后,会执行
done()
方法调用。由于这两个方法都是在中执行的,我希望在所有
process()
调用完成后执行
done()。换言之:

预期: 结果: 示例代码
导入java.awt.BorderLayout;
导入java.awt.Dimension;
导入java.awt.Graphics;
导入java.awt.event.ActionEvent;
导入java.util.List;
导入javax.swing.AbstractAction;
导入javax.swing.Action;
导入javax.swing.JButton;
导入javax.swing.JFrame;
导入javax.swing.JPanel;
导入javax.swing.JScrollPane;
导入javax.swing.JTextArea;
导入javax.swing.SwingUtilities;
导入javax.swing.SwingWorker;
公开课演示{
私人荡秋千工人;
私人JTEXTEXTAREA textArea;
私人行动开始,停止;
私有void createAndShowGui(){
startAction=新的抽象动作(“开始写作”){
@凌驾
已执行的公共无效操作(操作事件e){
Demo.this.startWriting();
此.setEnabled(false);
stopAction.setEnabled(真);
}
};
stopAction=新的抽象操作(“停止编写”){
@凌驾
已执行的公共无效操作(操作事件e){
Demo.this.stopWriting();
此.setEnabled(false);
startAction.setEnabled(真);
}
};
JPanel buttonPanel=新的JPanel();
添加(新的JButton(startAction));
添加(新的JButton(停止操作));
textArea=新的JTextArea(30,50);
JScrollPane scrollPane=新的JScrollPane(textArea);
JFrame=新JFrame(“测试”);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(滚动窗格);
框架。添加(按钮面板,边界布局。南);
frame.pack();
frame.setLocationRelativeTo(空);
frame.setVisible(true);
}
私有void startWriting(){
停止写作();
工人=新的SwingWorker(){
@凌驾
受保护的Void doInBackground()引发异常{
而(!isCancelled()){
发布(“编写…\n”);
}
返回null;
}
@凌驾
受保护的无效进程(列表块){
String String=chunks.get(chunks.size()-1);
textArea.append(字符串);
}
@凌驾
受保护的void done(){
textArea.append(“已停止!\n”);
}
};
worker.execute();
}
私有void stopWriting(){
if(worker!=null&!worker.isCancelled()){
worker.cancel(true);
}
}
公共静态void main(字符串[]args){
SwingUtilities.invokeLater(新的Runnable(){
@凌驾
公开募捐{
新建演示().createAndShowGui();
}
});
}
}

简短回答:

这是因为publish()不会直接调度
进程
,它会设置一个计时器,在
延迟
后触发EDT中进程()块的调度。因此,当工作进程被取消时,仍有一个计时器等待使用上次发布的数据安排进程()。使用计时器的原因是为了实现优化,其中可以使用多个发布的组合数据执行单个进程

长答案:

让我们看看publish()和cancel如何相互作用,为此,让我们深入了解一些源代码

首先是简单的部分,
cancel(true)

此取消操作将调用以下代码:

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;
}
doneEDT()
代码是:

private void doneEDT() {
    Runnable doDone =
        new Runnable() {
            public void run() {
                done(); // <-----
            }
        };
    if (SwingUtilities.isEventDispatchThread()) {
        doDone.run(); // <-----
    } else {
        doSubmit.add(doDone);
    }
}
然而,我们仍然从process()收到一条“Writing…”消息。让我们看看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);  // <-----
}
因此,
publish()
实际上是将块添加到一些内部ArrayList
参数中,然后调用
submit()
。我们刚刚看到submit只调用
doSubmit.add(this)
,这是一个非常相同的
add
方法,因为
doProcess
doSubmit
都扩展了
acgregativerunnable
,但是这次
V
可运行的
,而不是
doProcess
中的
字符串。因此,块是调用
进程(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(); // <-----
    }
}
我们现在知道这是可行的,因为在延迟时间间隔内发生的所有发布都在将它们的
args
添加到我们看到的
参数的内部变量中,并且
过程(块)
将一次性执行所有这些数据

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

很难判断这是否是一个bug,处理后台线程发布的数据可能是有意义的,因为工作实际上已经完成,并且您可能会对使用尽可能多的信息更新GUI感兴趣(例如,如果
process()
正在这样做的话)。然后呢
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;
}
future = new FutureTask<T>(callable) {
    @Override
    protected void done() {
        doneEDT();  // <-----
        setState(StateValue.DONE);
    }
};
private void doneEDT() {
    Runnable doDone =
        new Runnable() {
            public void run() {
                done(); // <-----
            }
        };
    if (SwingUtilities.isEventDispatchThread()) {
        doDone.run(); // <-----
    } else {
        doSubmit.add(doDone);
    }
}
while(!isCancelled()) {
    textArea.append("Calling publish\n");
    publish("Writing...\n");
}
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);  // <-----
}
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(); // <-----
    }
}
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(); // <-----
    }
}
 publish("1");
 publish("2", "3");
 publish("4", "5", "6");

might result in:
 process("1", "2", "3", "4", "5", "6")
@Override
protected void process(List<String> chunks) {
    if (isCancelled()) return;
    String string = chunks.get(chunks.size() - 1);
    textArea.append(string);
}
# this is how you say Worker... is a subclass of SwingWorker in Python/Jython
class WorkerAbleToWaitForPublicationToFinish( javax.swing.SwingWorker ):

    # __init__ is the constructor method in Python/Jython
    def __init__( self ):

        # we just add an "attribute" (here, publication_counter) to the object being created (self) to create a field of the new object
        self.publication_counter = java.util.concurrent.atomic.AtomicInteger()

    def await_processing_of_all_chunks( self ):
        while self.publication_counter.get():
            time.sleep( 0.001 )

    # fully functional override of the Java method     
    def process( self, chunks ):
        for chunk in chunks:
            pass
            # DO SOMETHING WITH EACH CHUNK

        # decrement the counter by the number of chunks received
        # NB do this AFTER dealing with the chunks 
        self.publication_counter.addAndGet( - len( chunks ) )

    # fully functional override of the Java method     
    def publish( self, *chunks ):
        # increment the counter by the number of chunks received
        # NB do this BEFORE publishing the chunks
        self.publication_counter.addAndGet( len( chunks ))
        self.super__publish( chunks )
    engine.update_xliff_task.get()
    engine.update_xliff_task.await_processing_of_all_chunks()
class CounterLatch():
    def __init__( self, initial = 0, wait_value = 0, lift_on_reached = True ):
        self.count = java.util.concurrent.atomic.AtomicLong( initial )
        self.signal = java.util.concurrent.atomic.AtomicLong( wait_value )

        class Sync( java.util.concurrent.locks.AbstractQueuedSynchronizer ):
            def tryAcquireShared( sync_self, arg ):
                if lift_on_reached:
                    return -1 if (( not self.released.get() ) and self.count.get() != self.signal.get() ) else 1
                else:
                    return -1 if (( not self.released.get() ) and self.count.get() == self.signal.get() ) else 1
            def tryReleaseShared( self, args ):
                return True

        self.sync = Sync()
        self.released = java.util.concurrent.atomic.AtomicBoolean() # initialised at False

    def await( self, *args ):
        if args:
            assert len( args ) == 2
            assert type( args[ 0 ] ) is int
            timeout = args[ 0 ]
            assert type( args[ 1 ] ) is java.util.concurrent.TimeUnit
            unit = args[ 1 ]
            return self.sync.tryAcquireSharedNanos(1, unit.toNanos(timeout))
        else:
            self.sync.acquireSharedInterruptibly( 1 )

    def count_relative( self, n ):
        previous = self.count.addAndGet( n )
        if previous == self.signal.get():
            self.sync.releaseShared( 0 )
        return previous
self.publication_counter_latch = CounterLatch() 
self.publication_counter_latch.count_relative( len( chunks ) )
self.super__publish( chunks )
worker.publication_counter_latch.await()