在EDT上调用exit()之前,SecondaryLoop.enter()不会阻塞 [英] SecondaryLoop.enter() not blocking until exit() is called on the EDT

查看:84
本文介绍了在EDT上调用exit()之前,SecondaryLoop.enter()不会阻塞的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

出于某种原因,当我调用 SecondaryLoop.enter() 在AWT事件调度线程(EDT)上,它不等待

For some reason when I call SecondaryLoop.enter() on the AWT Event Dispatch Thread (EDT), it does not wait for SecondaryLoop.exit() to be called before unblocking.

由于我认为SecondaryLoop并不是一个非常知名的类,因此我将做一个简短的概述:

Since I think SecondaryLoop is not a very well-known class, I'll give a brief overview:

通常,在EDT上运行任何长时间执行或阻塞的代码是一个坏主意,因为这样一来,您的应用程序在该代码终止之前将不会响应任何事件. EventQueue.createSecondaryLoop()允许您创建一个新的事件循环,该循环将处理事件,从而使您可以阻止EDT,而不会失去响应能力.这就是摆动模式对话框的用途,它使您可以在等待对话框关闭时阻止EDT,但仍允许对话框本身上的控件能够操作.

In general, it is a bad idea to have any long-executing or blocking code running on the EDT because then your app will not be responsive to any events until that code terminates. The EventQueue.createSecondaryLoop() allows you to create a new event loop that will handle events, allowing you to block the EDT without loss of responsiveness. This is what swing modal dialogs use to allow you to block your EDT while you wait for the dialog to be closed, but still allow controls on the dialog itself to be able to operate.

创建SecondaryLoop实例后,您应该可以调用enter(),并且该实例应该一直阻塞,直到调用exit()为止.

After creating your SecondaryLoop instance, you should be able to call enter() and it should block until exit() is called.

来自文档

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

This method can be called by any thread including the event dispatch thread. This thread will be blocked until the exit() method is called or the loop is terminated. A new secondary loop will be created on the event dispatch thread for dispatching events in either case.

我不太确定当它说或循环终止"时是什么意思.那可能是我的问题.

I'm not entirely sure what it means when it says "or the loop is terminated" though. That could be my issue.

在除EDT之外的其他线程上调用enter()方法,正如我期望的那样阻塞:

The calling the enter() method on a thread other than EDT, blocks as I would expect:

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

输出:

Enter Loop

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

However, if we call it on the EDT, it blocks for about a second, but then continues on:

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),我更新了代码以防止出现任何可能的垃圾回收问题:

Per the comment by tevemadar (thanks BTW), I have updated the code to prevent any sort of possible garbage collection issue:

//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

因此,尽管这是一个很好的建议,但这似乎不是我的问题.

So, while it was a good suggestion, that does not appear to be my issue.

这似乎与文档(和SecondaryLoop的整个目的对我来说是矛盾的.我是否缺少某些内容?

This seems pretty contradictory to the documentation (and the whole purpose of SecondaryLoop to me. Am I missing something?

操作系统:Windows 10

OS: 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循环不断添加更多事件.似乎添加计时器使循环保持活动并使其阻塞:

Update

On a hunch, I tried adding a timer that continually adds more events to the EDT loop. It seems that adding the timer keeps the loop alive and makes it blocking:

//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出于某种原因触发的...).

With that code, it hangs as I expect, and if I put in some code that calls the exit() method on the loop after some time, it terminates as I would expect. So it seems that the loop must terminate itself once it has gone a certain amount of time without an event (but only if it was originally triggered from the EDT for some reason...).

我想我可以添加需要使用此功能的计时器,但是我认为这绝对是一种解决方法,而不是解决方法.

I suppose I can add timers that do nothing whenever I need to use this feature, but that is definitely more of a work-around hack than a fix in my opinion.

推荐答案

弄清楚了(至少这个特定的问题,我还有更多相关的问题,但我希望我能自己解决).

Figured it out (at least this specific problem, I still have some more related issues, but I'm hoping I can figure them out on my own).

我决定开始在Java源代码中进行调试,我意识到由于java.awt.EventQueue中的这一段,我的线程变得不受阻碍:

I decided to start debugging around in the java source code and I realized that my thread was getting unblocked due to this segment in 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()之前终止.

In my case src was AWTAutoShutdown, which resulted in my secondary loop to terminate before I called exit().

我找到了这篇文章,这说明为了确保事件队列最终在所有窗口都被放置时终止,awt确定何时所有组件不再可显示并且事件队列为空,然后等待1秒,然后使用AWTAutoShutdown类作为终止事件队列并允许JVM终止的源. 1秒超时就是为什么我观察到它会挂起一小段原因的原因.

I found this article which explains that in order to make sure the event queue eventually terminates when all windows are disposed, awt determines when all components are no longer displayable and the event queue is empty, then waits for 1 second, then triggers the event with the AWTAutoShutdown class as the source which terminates the event queue and allows the JVM to terminate. That 1 second timeout is why I observed that it would hang for a little bit.

这说明了为什么添加计时器使我的代码正常工作(因为我每半秒添加一个事件,并且AWTAutoShutdown的超时为1秒,所以事件队列将保持活动状态).

This explains why adding a timer made my code work (since I was adding an event every half second and the timeout for AWTAutoShutdown is 1 second, the event queue would be kept alive).

所有这些的用例是基本上创建一个EDT安全的信号量,即使在EDT上等待事件,事件也将继续执行(我用来显示来自Swing应用程序的JavaFX对话框并使其执行)表现得像一个原生的挥杆模态对话框).因此,在我的实际用例中,这应该可以正常工作(因为在我的实际应用程序中始终应该显示一些挥杆组件).但是,我什至没有实际试用过我的实际用例.作为TDD的忠实拥护者,我首先专注于JUnit测试,该测试实际上并未创建任何UI组件.

The use case for all of this is to basically create an EDT-safe semaphore that will allow events to keep being executed even when it's waited for on the EDT (which I use to display JavaFX dialogs from a Swing application and make it behave like a native swing modal dialog). So in my actual use case, this should work just fine (because there should always be some swing component displaying in my actual applications). However, I hadn't even actually tried out my actual use case. Being a big believer in TDD, I first focused on my JUnit tests, which didn't actually create any UI components.

因此,我对一个带有GUI的小型虚拟应用程序进行了快速测试,它工作正常.我将在我的单元测试中添加500毫秒计时器,并在每次测试之前将其启动并停止.

So, I did a quick test with a little dummy app that does have a GUI, it works just fine. I'm just going to add my 500 ms timer into my unit tests and start it and stop it before each test.

这样做之后,我的某些测试仍然遇到一些问题,但是我从原始问题中得到的最小的可验证示例就可以了.我将深入研究其余的测试失败,并希望自己解决这些失败.如果这似乎是一个相关问题,那么我将添加一个新的SO问题,并在此处添加一个链接.

After doing that, I'm still running into some issues with some of my tests, but my minimal verifiable example from my original question works just fine. I'll dig into the remaining test failures and hopefully figure them out on my own. If it seems to be a related issue, then I'll just add a new SO question and put a link to it here.

这篇关于在EDT上调用exit()之前,SecondaryLoop.enter()不会阻塞的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆