首页 > 解决方案 > 在 EDT 上调用 exit() 之前,SecondaryLoop.enter() 不会阻塞

问题描述

概括

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

背景

由于我认为SecondaryLoop不是一个非常知名的课程,我将简要概述一下:

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

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

从文档

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

不过,我不完全确定它说“或循环终止”是什么意思。那可能是我的问题。

测试代码

在 EDT 以外的线程上调用该enter()方法会阻塞,正如我所料:

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

输出:

Enter Loop

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

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)");

输出:

Enter Loop
Done (we should never get here)

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

//Storing loop in array as a quick hack to get past the
// "final or effectively final" issue when using this in the invokeAndWait
SecondaryLoop loop[] = new SecondaryLoop[1];

System.out.println("Enter Loop");
try {
    SwingUtilities.invokeAndWait(() -> {
        loop[0] = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
        loop[0].enter();
    });
} catch (InvocationTargetException | InterruptedException e) {
    e.printStackTrace();
}
System.out.println("Done (we should never get here)");
//Just printing this to make sure that it is used after the invokeAndWait is done. This is just
//to make sure there isn't some sort of optimization thing that is deciding that we don't
//need this anymore and allowing the loop to be garbage collected
System.out.println(loop[0]);

输出:

Enter Loop
Done (we should never get here)
java.awt.WaitDispatchSupport@2401f4c3

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

这似乎与文档(以及对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)

更新

凭直觉,我尝试添加一个计时器,该计时器不断将更多事件添加到 EDT 循环中。似乎添加计时器使循环保持活动状态并使其阻塞:

//Add a keep alive timer which adds an event to the EDT for every 0.5 sec
new Timer(500, null).start();

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)");

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

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

标签: javaawtevent-dispatch-thread

解决方案


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

我决定在 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);
            }
        }
    }

在我的情况下srcAWTAutoShutdown这导致我的辅助循环在我调用之前终止exit()

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

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

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

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

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


推荐阅读