1

I have a situation in which I want to annunciate (possibly multiple, sequential) events occurring on a non event dispatch thread. I read the following in the JOptionPane documentation and supposed that I could do what is shown in the below SSOC.

"All dialogs are modal. Each showXxxDialog method blocks the caller until the user's interaction is complete."

However, when the example is executed, both JOPs are created and stacked on the display. Based on the above, I was expecting that the first JOP would display and block the EDT awaiting user input; then when the user dismissed that one, the 2nd one would display and block, awaiting user input. What am I not understanding?

Things I already know:

  • This topic is discussed in JOptionPane#showMessageDialog(...) does not block on the EDT, but the responses do not answer my question.

  • This answer by https://stackoverflow.com/users/1542183/david to this question helps: How to prompt a confirmation dialog box in the middle of non event dispatching thread when the writer quotes the Javadoc for Dialog.show() saying "It is permissible to show modal dialogs from the event dispatching thread because the toolkit will ensure that another event pump runs while the one which invoked this method is blocked.". I am thinking I understand that there are 2 event pumps, so I would think after 2 JOPs, they would both be blocked. But I upped the count to 5 in my example and the results are the same; all 5 JOPs are created and displayed one on top of the other.

  • If invokeLater() is replaced with invokeAndWait(), the expected behavior occurs. But why is this necessary?

  • There is no exit() in the example because you wouldn't see the dialogs if there were. You have to kill the JVM.

    public class DoubleVision {
      public static void main( String args[] ) {
        javax.swing.SwingUtilities.invokeLater( new Runnable() {
          public void run() {
            javax.swing.JOptionPane.showMessageDialog( null, "Enqueued first" );
            } // run()
          });
    
        javax.swing.SwingUtilities.invokeLater( new Runnable() {
          public void run() {
            javax.swing.JOptionPane.showMessageDialog( null, "Enqueued second" );
            } // run()
          });
    
      } // main()
    } // DoubleVision
    
tgdavies
  • 10,307
  • 4
  • 35
  • 40
  • 1
    Java swing has an event thread with its own event queue where an event is an mouse click,button click etc. The invokeLater adds your showMessageDialog as an instruction to the swings event queue which will be executed by the swing thread in its own time therefore it doesn't execute it immediately and your main moves to the next instruction whereas invokeAndWait adds the instruction and waits for the thread to execute it. Just directly call it in your main without the invokeLater or invokeWait. – Sync it Nov 07 '20 at 04:11
  • 2
    I get the impression you are trying to code some kind of wizard or breadcrumb. Using a series of `JOptionPane`s is not a good way to do it. I suggest that you search for ___java swing breadcrumb___ or ___java swing wizard___. Maybe [this article](https://www.oracle.com/technical-resources/articles/javase/wizard.html) is appropriate? Or maybe you can use this [breadcrumb](http://www.jidesoft.com/javadoc/com/jidesoft/navigation/BreadcrumbBar.html)? – Abra Nov 08 '20 at 12:31

2 Answers2

1

If invokeLater() is replaced with invokeAndWait(), the expected behavior occurs. But why is this necessary?

Running the code on the EDT may not seem necessary and isn't in your example, but thread safety is important in larger applications. Thus, it's generally better to call the JOptionPane on the EDT.

In regards to why the expected behavior occurs, as @Sync it said, invokeAndWait waits for the thread to execute the instruction. With invokeLater, the thread asynchronously completes the instruction; it doesn't wait for the instruction to complete. Instead, it moves on to other events, in your case, your second JOptionPane.

Based on the above, I was expecting that the first JOP would display and block the EDT awaiting user input; then when the user dismissed that one, the 2nd one would display and block, awaiting user input. What am I not understanding?

With invokeLater, both instructions are executed, and you are forcing the instruction to be completed at another time and move on with other events.

"All dialogs are modal. Each showXxxDialog method blocks the caller until the user's interaction is complete."

The expected behavior of the dialog is shown as it works on the main thread. With invokeLater you are, in the general sense, overriding the dialog's behavior.

Peter Alsen
  • 320
  • 2
  • 5
  • 15
1

I think I figured out what is happening in the example. After pondering the Dialog.show() code (which is not easy to ponder ...) and being tipped off by some OpenJavadoc on the www, I found the following in the package-private java.awt.EventDispatchThread class:

"The Thread starts a "permanent" event pump with a call to pumpEvents(Conditional) in its run() method. Event handlers can choose to block this event pump at any time, but should start a new pump (not a new EventDispatchThread) by again calling pumpEvents(Conditional). ..."

So, as intimated by the answer in the post referenced above, the "All dialogs are modal ..." statement is true - in a sense, but when the EDT is the executor that thread doesn't really "block". Each JOptionPane is wrapped in a JDialog, whose ctor is an "Event handler" in the vernacular of the EventDispatchThread doc. The dialog is then realized via JDialog.show(). JDialog.show() doesn't return until the dialog is dismissed, but it doesn't block the EDT either - instead it does some magic with the focus system and event filters to produce the modal effect - AND immediately launches a new event pump (executing on the same EDT) as "Event handlers" are instructed to do in the EventDispatchThread doc. This is all unwound when the user dismisses the JOP.

In my example the caller is not the EDT, so invokeLater() dutifully posts all the JOP ctors on the system event queue, as I desire. The running event pump processes the first showMessageDialog() and does not return, but does start a new event pump on the EDT, which immediately processes the 2nd showMessageDialog(), turning it into a JDialog which does not return but does start a new event pump on the EDT, ad infinitum. Each time, JDialog works its focus system magic, meaning that the last dialog displayed is the only one that is accessible in the GUI - this focus trick makes the Swing "appear" to be waiting on the last JOP - when in reality the EDT continues happily along. It has to be something like this, else the GUI input to dismiss the JOP would never be processed and it really would hang.

This behavior explains why (for instance) you find the following in the Javadoc for javax.swing.SwingWorker.get():

"When you want the SwingWorker to block on the Event Dispatch Thread we recommend that you use a modal dialog."

=========================

To obtain the desired FIFO behavior described in the OP while the non-EDT continues to work, I settled on the following. Constructive comments in re reliability or pattern are welcome.

Some things to note:

  • For brevity I started the daemon thread in the ctor, which is not advisable. If using this, you should modify it as described in this comment: https://stackoverflow.com/a/28929746/3006394.
  • A fixed capacity queue is used. A LinkedBlockingQueue can be used if unbounded capacity (up to Integer.MAX_VALUE) is required.
  • If your needs extend to things such as the ability to shutdown the Announcer, monitor its progress, retrieve user input from each JOptionPane interaction, and so on, the java.util.concurrent.Executors class may provide a better framework than the simple Thread I use here.
import javax.swing.*;

public class TripleVision {
  public static void main( String args[] ) throws InterruptedException {
    Announcer myAnnouncer = new Announcer();
    
    myAnnouncer.announce( "Enqueued first" );
    myAnnouncer.announce( "Enqueued second" );
    myAnnouncer.announce( "Enqueued third" );

    System.out.println( "Continuing on, doing other things now" );
    Thread.sleep( 7000 );       // Just a kludge to prevent this example
                                // from terminating before the first JOP
    } // main()                 // is realized

  private static class Announcer {
    /* Operations of the BlockingQueue are atomic, so we do not have to
     * synchronize our accesses. */
    private final java.util.concurrent.BlockingQueue
        requests = new java.util.concurrent.ArrayBlockingQueue ( 15, false );

    private Runnable annTask = new Runnable() {
      public void run() {
        try {
          while ( true ) {
            final Object msg = requests.take();
            SwingUtilities.invokeAndWait( new Runnable() {
              public void run() {
                JOptionPane.showMessageDialog( null, msg );
                } // run()
              }   // annonymous Runnable
              );  // invokeAndWait()
            } // while
          } // try
        catch ( InterruptedException ex ) {}       // Not used
        catch ( InvocationTargetException ex ) {}  // Bad karma in run()
          ex.printStackTrace();
          }  // catch
        } // run()
      };  // annTask Runnable

    public Announcer() {
      Thread thd = new Thread( annTask );
      thd.setName( "Announcer-" + thd.getName() );
      thd.setDaemon( true );      // JVM will terminate it w/o complaint
      thd.start();
      } // Announcer()

    public void announce( Object announcement ) {
      requests.offer( announcement );
      } // announce()

    } // Announcer

  } // TripleVision