在 EDT 上调用 exit() 之前,SecondaryLoop.enter() 不会阻塞

Nat*_*teW 5 java awt event-dispatch-thread

概括

由于某种原因,当我调用SecondaryLoop.enter()AWT 事件调度线程 (EDT) 时,它不会等待SecondaryLoop.exit()被调用才解除阻塞。

背景

由于我认为SecondaryLoop不是一个非常知名的课程,我简单介绍一下:

一般来说,在 EDT 上运行任何长时间执行或阻塞的代码是一个坏主意,因为这样您的应用程序将不会响应任何事件,直到该代码终止。允许EventQueue.createSecondaryLoop()您创建一个新的事件循环来处理事件,从而允许您在不损失响应能力的情况下阻止 EDT。这就是 Swing 模式对话框用来允许您在等待对话框关闭时阻止 EDT,但仍然允许对话框本身的控件能够运行。

创建SecondaryLoop实例后,您应该能够调用enter()并且它应该阻塞直到exit()被调用。

来自文档

此方法可以由任何线程(包括事件分派线程)调用。该线程将被阻塞,直到调用 exit() 方法或终止循环。在任何一种情况下,都会在事件分派线程上创建一个新的辅助循环来分派事件。

不过,我不完全确定“或循环终止”时的含义。这可能是我的问题。

测试代码

enter()在 EDT 之外的线程上调用该方法会按照我的预期阻塞:

System.out.println("Enter Loop");
Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop().enter();
System.out.println("Done (we should never get here)");
Run Code Online (Sandbox Code Playgroud)

输出:

Enter Loop
Run Code Online (Sandbox Code Playgroud)

但是,如果我们在 EDT 上调用它,它会阻塞大约一秒钟,然后继续:

Enter Loop
Run Code Online (Sandbox Code Playgroud)

输出:

Enter Loop
Done (we should never get here)
Run Code Online (Sandbox Code Playgroud)

根据 tevemadar 的评论(谢谢顺便说一句),我更新了代码以防止任何可能的垃圾收集问题:

System.out.println("Enter Loop");
try {
    SwingUtilities.invokeAndWait(() -> Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop().enter());
} catch (InvocationTargetException | InterruptedException e) {
    e.printStackTrace();
}
System.out.println("Done (we should never get here)");
Run Code Online (Sandbox Code Playgroud)

输出:

Enter Loop
Done (we should never get here)
java.awt.WaitDispatchSupport@2401f4c3
Run Code Online (Sandbox Code Playgroud)

因此,虽然这是一个很好的建议,但这似乎不是我的问题。

这似乎与文档(以及SecondaryLoop对我来说的整个目的)非常矛盾。我错过了什么吗?

环境

操作系统:Windows 10

爪哇:

C:\Program Files\Java\jre8\bin>java.exe -version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
Run Code Online (Sandbox Code Playgroud)

更新

凭直觉,我尝试添加一个计时器,不断向 EDT 循环添加更多事件。看来添加计时器可以使循环保持活动状态并使其阻塞:

Enter Loop
Done (we should never get here)
Run Code Online (Sandbox Code Playgroud)

使用该代码,它会按照我的预期挂起,如果我放入一些代码exit()在一段时间后调用循环上的方法,它就会按照我的预期终止。因此,似乎循环一旦经过一定时间而没有发生事件,就必须自行终止(但前提是它最初是由于某种原因从 EDT 触发的......)。

我想我可以添加计时器,当我需要使用此功能时,它什么也不做,但在我看来,这绝对是一种变通方法,而不是修复。

Nat*_*teW 1

弄清楚了(至少这个具体问题,我还有一些更多相关问题,但我希望我能自己解决)。

我决定开始在 java 源代码中进行调试,我意识到我的线程由于以下部分而被解除阻塞java.awt.EventQueue

    /**
     * Called from dispatchEvent() under a correct AccessControlContext
     */
    private void dispatchEventImpl(final AWTEvent event, final Object src) {
        event.isPosted = true;
        if (event instanceof ActiveEvent) {
            // This could become the sole method of dispatching in time.
            setCurrentEventAndMostRecentTimeImpl(event);
            ((ActiveEvent)event).dispatch();
        } else if (src instanceof Component) {
            ((Component)src).dispatchEvent(event);
            event.dispatched();
        } else if (src instanceof MenuComponent) {
            ((MenuComponent)src).dispatchEvent(event);
        } else if (src instanceof TrayIcon) {
            ((TrayIcon)src).dispatchEvent(event);
        } else if (src instanceof AWTAutoShutdown) {
            if (noEvents()) {
                dispatchThread.stopDispatching();
            }
        } else {
            if (getEventLog().isLoggable(PlatformLogger.Level.FINE)) {
                getEventLog().fine("Unable to dispatch event: " + event);
            }
        }
    }

Run Code Online (Sandbox Code Playgroud)

就我而言srcAWTAutoShutdown这导致我的辅助循环在我调用之前终止exit()

我发现这篇文章解释了为了确保事件队列最终在所有窗口都被处理后终止,awt 确定何时所有组件不再可显示并且事件队列为空,然后等待 1 秒,然后使用该类AWTAutoShutdown作为终止事件队列并允许 JVM 终止的源。这 1 秒超时就是我观察到它会挂起一点的原因。

这解释了为什么添加计时器使我的代码正常工作(因为我每半秒添加一个事件并且超时为AWTAutoShutdown1 秒,因此事件队列将保持活动状态)。

所有这些的用例基本上是创建一个 EDT 安全信号量,即使事件在 EDT 上等待(我用它来显示来自 Swing 应用程序的 JavaFX 对话框,并使其表现得像一个本机 Swing 模式对话框)。因此,在我的实际用例中,这应该可以正常工作(因为在我的实际应用程序中应该始终显示一些摆动组件)。然而,我什至还没有真正尝试过我的实际用例。作为 TDD 的忠实拥护者,我首先专注于 JUnit 测试,它实际上并没有创建任何 UI 组件。

所以,我用一个有 GUI 的小虚拟应用程序做了一个快速测试,它工作得很好。我只是将 500 毫秒计时器添加到我的单元测试中,并在每次测试之前启动和停止它。

这样做之后,我的一些测试仍然遇到一些问题,但我最初问题中的最小可验证示例工作得很好。我将深入研究剩余的测试失败,并希望自己解决它们。如果这似乎是一个相关问题,那么我将添加一个新的 SO 问题并在此处放置一个链接。