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