我已经与SwingWorker一起工作了一段时间,并最终出现了奇怪的行为,至少对我而言。我清楚地了解,由于性能原因,一次调用中合并了多个对publish()方法的调用。这对我来说非常有意义,我怀疑SwingWorker会保留某种队列来处理所有调用。
根据教程和API,当SwingWorker结束执行时,doInBackground()正常完成,或者从外部取消了工作线程,然后调用done()方法。到目前为止,一切都很好。
但是我有一个示例(类似于教程中显示的示例),其中在执行process()
方法 之后
done()
执行了一些方法调用。由于这两种方法都在事件调度线程中执行,所以我希望done()
所有process()
调用完成后才能执行。换一种说法:
Writing...
Writing...
Stopped!
Writing...
Stopped!
Writing...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Demo {
private SwingWorker<Void, String> worker;
private JTextArea textArea;
private Action startAction, stopAction;
private void createAndShowGui() {
startAction = new AbstractAction("Start writing") {
@Override
public void actionPerformed(ActionEvent e) {
Demo.this.startWriting();
this.setEnabled(false);
stopAction.setEnabled(true);
}
};
stopAction = new AbstractAction("Stop writing") {
@Override
public void actionPerformed(ActionEvent e) {
Demo.this.stopWriting();
this.setEnabled(false);
startAction.setEnabled(true);
}
};
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(new JButton(startAction));
buttonsPanel.add(new JButton(stopAction));
textArea = new JTextArea(30, 50);
JScrollPane scrollPane = new JScrollPane(textArea);
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(scrollPane);
frame.add(buttonsPanel, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void startWriting() {
stopWriting();
worker = new SwingWorker<Void, String>() {
@Override
protected Void doInBackground() throws Exception {
while(!isCancelled()) {
publish("Writing...\n");
}
return null;
}
@Override
protected void process(List<String> chunks) {
String string = chunks.get(chunks.size() - 1);
textArea.append(string);
}
@Override
protected void done() {
textArea.append("Stopped!\n");
}
};
worker.execute();
}
private void stopWriting() {
if(worker != null && !worker.isCancelled()) {
worker.cancel(true);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Demo().createAndShowGui();
}
});
}
}
简短答案:
发生这种情况是因为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 publish\n");
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。我跳过了一些细节,“运行”调用链如下所示:
(*)布尔值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是否也被取消!
@Override
protected void process(List<String> chunks) {
if (isCancelled()) return;
String string = chunks.get(chunks.size() - 1);
textArea.append(string);
}
要使done()在最后一个process()之后执行比较棘手,例如done可以只使用一个计时器,该计时器将在>
DELAY之后安排实际的done()工作。尽管我不认为这是常见的情况,因为如果您取消了它,那么当我们知道实际上正在取消所有将来的执行时,再错过一个process()也不重要。
问题内容: 我创建了一个for循环,该循环循环了元素出现在容器中的次数。for循环从HTML捕获一些数据,并创建一个JSON url,然后将返回一个值。然后应将该值添加到适当位置的HTML中。 问题似乎是for循环在进行所有Ajax调用之前完成,因此仅将最后一个值添加到HTML。我以为可以确保readystate等于4,但是该解决方案不起作用。我还尝试将完整而不是成功用作Ajax事件。有什么见解吗
我有一个用于交互式过渡的自定义动画师。还有一个,根据过渡进度设置为。效果的动画代码如下: 我通过调用它,当从它到第一个的转换开始时,它在第二个上调用。 然而,我这里有一个问题。在动画结束之前调用完成块。当我第一次运行转换(没有取消它)时,它工作得很好,但在随后的运行过程中,它就不工作了。 我也曾尝试将动画添加到我的动画师中,但也没有成功。 此外,当我取消转换时,在实际动画结束之前调用完成块(在这种
问题内容: 所述javax.servlet.Filter的对象可以用于认证使用二者(其中过滤器需要赶上请求需要做任何servlet工作需要之前)和XSLT转换(其中的servlet需要是完全完成生成的内容)。它什么时候真正执行? 我知道这是依赖于实现的(在Web容器上),但这似乎是所有问题都需要解决的问题。 也许在某个地方为每个向Web容器注册的过滤器设置了一个配置选项? 额外: 另外,什么决定了
您的Google Maps代码结构如下所示: 回调? JavaScriptpromise? 您能想到的其他方法吗? 同样,这里所建议的信号量是一种可行的方法吗?
javax。servlet。Filter对象既可以用于身份验证(在需要完成任何servlet工作之前,过滤器需要捕获请求),也可以用于XSLT转换(servlet需要完全生成内容)。什么时候执行? 我知道这取决于实现(取决于web容器),但这似乎是所有人都需要解决的问题。 也许在web容器的每个过滤器注册的地方都设置了一个配置选项? 其他: 此外,什么控制过滤器的执行顺序?为什么FooFilter
问题内容: 我正在使用 dialogflow-fulfillment- nodejs 客户端开发Dialogflow webhook,以查找城市的温度。在使用服务获取城市温度时,一切正常,还会生成正确的响应,但是即使调用正确的方法,也不会将响应发送给用户。 这是Dialogflow GitHub存储库中的问题 码 控制台日志 当注释的代码运行时 当未注释的代码运行时 NodeJS Dialogfl