【问题标题】:invokeAndWait seems to cause the application to freeze intermittentlyinvokeAndWait 似乎导致应用程序间歇性冻结
【发布时间】:2013-11-01 15:20:54
【问题描述】:

在后台发生的进程会触发回调以提出各种问题。

在这种情况下,问题是“可以迁移您的数据吗?”,所以我必须询问用户。由于我们必须在 EDT 上完成所有 Swing 工作,因此最终看起来像这样(我只删除了 cmets,引用了我们自己的便捷方法和 allowMigration() 的参数——除此之外,其他一切都相同):

public class UserMigrationAcceptor implements MigrationAcceptor {
    private final Window ownerWindow;
    public UserMigrationAcceptor(Window ownerWindow) {
        this.ownerWindow = ownerWindow;
    }

    // called on background worker thread
    @Override
    public boolean allowMigration() {
        final AtomicBoolean result = new AtomicBoolean();
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    result.set(askUser());
                }
            });
        } catch (InterruptedException e) {
            Thread.currentThread.interrupt();
            return false;
        } catch (InvocationTargetException e) {
            throw Throwables.propagate(e.getCause());
        }
        return result.get();
    }

    // called on EDT
    private boolean askUser() {
        int answer = JOptionPane.showConfirmDialog(ownerWindow, "...", "...",
                                                   JOptionPane.OK_CANCEL_OPTION);
        return answer == JOptionPane.OK_OPTION;
    }
}

发生的情况是,在某些情况下,在确认或取消出现的对话框后,Swing 似乎进入了以下状态:

  • JOptionPane 不再可见
  • 事件队列中没有任何待处理的内容
  • 后台线程卡在#invokeAndWait内部,等待InvocationEvent#isDispatched()返回true

我们在这里做错了什么,还是我在查看 Swing/AWT 中的错误?

唯一值得注意的另一件事是,这是模态对话框的第二级。有一个显示操作进度的模式对话框,然后此确认对话框将进度对话框作为其父项。

更新 1: 以下是当前阻止 EDT 的地方:

java.lang.Thread.State: WAITING
      at sun.misc.Unsafe.park(Unsafe.java:-1)
      at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
      at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
      at java.awt.EventQueue.getNextEvent(EventQueue.java:543)
      at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:211)
      at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
      at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:154)
      at java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:182)
      at java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:221)
      at java.security.AccessController.doPrivileged(AccessController.java:-1)
      at java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:219)
      at java.awt.Dialog.show(Dialog.java:1082)
      at java.awt.Component.show(Component.java:1651)
      at java.awt.Component.setVisible(Component.java:1603)
      at java.awt.Window.setVisible(Window.java:1014)
      at java.awt.Dialog.setVisible(Dialog.java:1005)
      at com.acme.swing.progress.JProgressDialog$StateChangeListener$1.run(JProgressDialog.java:200)
      at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:251)
      at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:733)
      at java.awt.EventQueue.access$200(EventQueue.java:103)
      at java.awt.EventQueue$3.run(EventQueue.java:694)
      at java.awt.EventQueue$3.run(EventQueue.java:692)
      at java.security.AccessController.doPrivileged(AccessController.java:-1)
      at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
      at java.awt.EventQueue.dispatchEvent(EventQueue.java:703)
      at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
      at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
      at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:154)
      at java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:182)
      at java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:221)
      at java.security.AccessController.doPrivileged(AccessController.java:-1)
      at java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:219)
      at java.awt.Dialog.show(Dialog.java:1082)
      at javax.swing.JOptionPane.showOptionDialog(JOptionPane.java:870)

这里奇怪的是,底部的showOptionDialog() 是迁移提示,但更上方的Dialog#setVisible 是进度对话框。换句话说,不知何故,有时子对话框出现在父对话框之前,也许这就是破坏 Swing 的原因。

更新 2:

确实,我可以在测试程序中实现这一点,而无需使用我们自己的任何代码。虽然测试程序中的对话框定位不同,但挂起方式完全相同,只是重现性更高。 gist

【问题讨论】:

  • 如需尽快获得更好的帮助,请发帖SSCCE。顺便说一句,我怀疑它冻结了,因为 a) 代码阻塞了 EDT,或者 b) 使用了 invokeAndWait 而不是 invokeLater。 OTOH,当您发布 SSCCE 时,我会知道更多。
  • 困难,但会尝试。目前,EDT 正在等待处理新事件,但没有事件即将到来。据我所知, InvocationEvent.dispatch() 正在被调用,但 dispatched = true 行没有被第二次执行。 invokeLater 也无法使用,因为调用者希望我返回一个值。
  • 基本示例似乎对我有用......我能够在没有 isse 的情况下循环超过 100 次
  • 当这在生产中被报告时,我也无法以与报告问题相同的方式重现它。他们说它发生在确定对话框时,我发现它只发生在为我取消它时。它也只发生在第二次和随后的调用中,所以我认为这是某种竞争条件。同样,我的简单示例程序没有很好地模拟真正发生的事情,所以它也没有在那里发生。我不知道我需要在 100% 的情况下强制它发生的那种纳秒时间。 :(
  • 该示例在这里运行良好 - OS/LAF/jdk 发生故障时的任何细节? (不是很有帮助,我知道,只是说 ;-)

标签: java swing freeze invokeandwait


【解决方案1】:

我在自己的代码中也遇到了同样的问题。

您对JOptionPane.showOptionDialog() 的调用永远不会返回,因为当它的事件调度循环坐在那里等待用户输入时,一个计时器(或其他东西)已经触发并导致另一个模式对话框安装它自己的事件循环。在您的堆栈中,罪魁祸首是 JProgressDialog$StateChangeListener$1.run(),然后您可以看到它启动了自己的事件调度循环。

在您的 JProgressDialog 关闭之前,它不会退出循环,并且之前对 JOptionPane.showOptionDialog() 的调用将永远不会返回。

如果对话父级似乎暗示了事件队列不尊重的层次结构,这可能并不明显。

两种解决方案可能是避免模态进度对话框,或立即显示进度对话框。仅当事件线程的其余部分乐于坐下来等待它关闭时,从事件启动模式对话框才是一个好主意。

【讨论】:

  • 尽快显示进度对话框确实解决了这个问题(尽管我自己先弄清楚了,但从未看到有关此答案的通知)。我仍会将其标记为此特定问题的答案。
【解决方案2】:
  • invokeAndWait 必须在 EDT 之外调用,

  • 小心使用invokeAndWait,因为可以冻结整个 Swing GUI,被来自RepaintManager 的异常锁定,并非在所有情况下都只创建 GUI,重新布局,刷新一些方法,当从 repaint() 被调用时嵌套方法

  • 对于invokeAndWait 需要测试if (EventQueue.isDispatchThread()) { / if (SwingUtilities.isEventDispatchThread()) {

  • isDispatchThread()result.set(askUser()); 为true,没有任何副作用,输出是在EDT 上完成的,但是在invokeLater 内包装是一种很好的做法

  • 我看到了invokeAndWait 的一些用法,但仅在应用程序启动时,请改用invokeLater()

【讨论】:

  • 我想我会尝试使用invokeLater,但似乎我仍然必须阻止调用者直到它真正执行,所以基本上我将重新实现invokeAndWait。即,如果这样做有效,那么 invokeAndWait 几乎可以肯定有一个错误。 :(
  • true 是我从来没有看到在 Java >= 6, this is simple example about logics how EDT works 中实际使用 invokeAndWait,如果事件被刷新(API 中实现的一堆事件)然后 EDT 返回 false(有延迟30 秒),模拟,how is EDT implemented in API(ignore code inside if - else),你的代码需要这个 sceleton 作为 EDT 的通知器
【解决方案3】:

由于这种混乱,首先要怀疑的是,某处,正在从 EventQueue (EDT) 中调用 Swing 方法。 Swing 通常与多个线程一起工作得很好,但每隔一段时间就会发生这种情况。不幸的是,除了检查每个 swing 方法调用并确定它在 EDT 上之外,我没有其他方法可以解决此问题。 (注意,有一两个 Swing 方法可以在其他线程上运行,例如 repaint,但请检查每个的源代码和 Javadoc 以确定。)

【讨论】:

  • 我们实际上有一个自定义的重绘管理器,这样如果最终从 EDT 重绘任何东西,就会引发异常。这没有被触发,所以如果任何 Swing 的东西被取消 EDT,我至少知道它不是 repaint() ......但它可能是任何其他可能不会触发重绘的东西。 :(
  • 我认为repaint 是安全的,但我刚刚检查了源代码,它不是,我现在确定,从来没有。祝你好运。您必须发现每个 Swing 方法调用,然后确定该代码是否可以在 EDT 之外运行。我认为每个 Swing 方法应该 进行自己的检查,如果它在 EDT 上 not 则抛出异常,但这可能会减慢它的速度。
  • 我刚刚检查了来源,它不是 嗯......你是怎么得出这个结论的? AFAICS,它将 PaintEvents 发布到在 EventDispatchThread 上运行的 EventQueue。
  • @kleopatra:我回答问题时就是这么想的。我检查了 JComponent 中的重绘。乍一看,它(和 RepaintManager)不是线程安全的。更重要的是,文档没有说明线程安全。为 awt.Component 重绘看起来更安全,直接调用 postEvent。无论如何,在我做更多(1.4 后)研究之前,我不应该谈论重绘。
  • repaint() 记录 是安全的,但我们发现 99.999% 的 repaint() 调用是在错误线程上摆弄模型的副作用,所以从其他线程中阻止它已经引起了很多恶作剧。在我们没有选择的情况下执行此操作的少数组件中(例如,您正在绘制动画 GIF 的任何内容),我们放入客户端属性以表示我们知道该组件行为不良(当然,所有这些都只运行正在开发中。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
  • 1970-01-01
  • 2016-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多