Summary
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.
Background
Since I think SecondaryLoop
is not a very well-known class, I'll give a brief overview:
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.
After creating your SecondaryLoop
instance, you should be able to call enter()
and it should block until exit()
is called.
From the docs
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.
Test Code
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)");
Output:
Enter Loop
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)");
Output:
Enter Loop
Done (we should never get here)
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]);
Output:
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.
This seems pretty contradictory to the documentation (and the whole purpose of SecondaryLoop
to me. Am I missing something?
Environment
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)
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)");
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.