2

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

在这种情况下,问题是“可以迁移您的数据吗?”,所以我不得不问用户。因为我们必须在 EDT 上完成所有 Swing 工作,所以最终看起来像这样(我只删除了注释、对我们自己的便捷方法和参数的引用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:

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

4

3 回答 3

2

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

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

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

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

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

于 2013-11-01T07:20:54.853 回答
1
  • invokeAndWait必须从 EDT 中调用,

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

  • forinvokeAndWait需要测试if (EventQueue.isDispatchThread()) {/if (SwingUtilities.isEventDispatchThread()) {

  • 真的从isDispatchThread()你可以到result.set(askUser());)没有任何副作用,输出是在 EDT 上完成的,但是关于包装内部的良好实践invokeLater

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

于 2013-07-22T07:33:09.013 回答
0

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

于 2013-07-22T19:49:15.340 回答