【发布时间】:2020-01-31 04:07:29
【问题描述】:
总结
由于某种原因,当我在 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 的评论(感谢顺便说一句),我已更新代码以防止任何可能的垃圾收集问题:
//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
Java:
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 触发的......)。
我想我可以添加在需要使用此功能时什么都不做的计时器,但在我看来,这绝对是一种变通方法而不是修复。
【问题讨论】:
-
我认为你用错了:
SecondaryLoop应该被保留,所以你可以稍后调用它的exit()。因为这里没有保留,可能是 GC 介入了,实际实现中可能有一个终结器退出循环。 -
感谢 tevemadar。这是一个很好的建议,但是我遇到的实际代码确实存储了循环。这只是我最小的可重现示例(没有存储循环)。我创建了一个新的最小可重现示例,它确实存储了循环并更新了问题。不幸的是,问题仍然存在。
标签: java awt event-dispatch-thread