为什么JOptionPane.showMessageDialog不会像广告中那样在事件分发线程上阻塞?

问题描述

我遇到一种情况,在这种情况下,我想通告非事件调度线程上发生的(可能是多个顺序发生的)事件。我在JOptionPane文档中阅读了以下内容,并认为我可以执行下面的SSOC中显示的操作。

“所有对话框都是模式对话框。每个showXxxDialog方法都会阻止调用者,直到用户的交互完成。”

但是,当执行示例时,两个JOP都被创建并堆叠在显示器上。基于以上所述,我期望第一个JOP将显示并阻止EDT等待用户输入。然后当用户撤消该按钮时,第二个按钮将显示并阻止,等待用户输入。我不明白什么?

我已经知道的事情:

  • 主题JOptionPane#showMessageDialog(...) does not block on the EDT中进行了讨论,但是答复并未回答我的问题。

  • https://stackoverflow.com/users/1542183/david对此问题的回答有助于:How to prompt a confirmation dialog box in the middle of non event dispatching thread,当作者引用Dialog.show()的Javadoc时说:“允许从事件分派线程中显示模式对话框。因为该工具包将确保在调用方法的事件泵被阻止的同时运行另一个事件泵。”我以为我知道有2个事件泵,因此我认为在2个JOP之后,它们都会被阻塞。但是在我的示例中,我将计数增加到5,结果是相同的。所有5个JOP都创建并显示在另一个之上。

  • 如果将invokelater()替换为invokeAndWait(),则会发生预期的行为。但是为什么这是必要的?

  • 示例中没有exit(),因为如果有,您将看不到对话框。您必须杀死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
    

解决方法

如果将invokeLater()替换为invokeAndWait(),则会发生预期的行为。但是为什么这是必要的?

在EDT上运行代码似乎没有必要,在您的示例中也没有,但是在较大的应用程序中,线程安全性很重要。因此,通常最好在EDT上调用JOptionPane

关于发生预期行为的原因,正如@Sync所说,invokeAndWait等待线程执行指令。使用invokeLater,线程异步完成指令;它不等待指令完成。取而代之的是,它会转移到其他事件上,例如您的第二个JOptionPane

基于上述,我期望第一个JOP将显示并阻止EDT等待用户输入;然后当用户撤消该按钮时,第二个按钮将显示并阻止,等待用户输入。我不明白什么?

使用invokeLater,两条指令都被执行,并且您迫使该指令在另一时间完成并继续其他事件。

“所有对话框都是模式对话框。每个showXxxDialog方法都会阻止调用者,直到用户的交互完成。”

在主线程上工作时,将显示对话框的预期行为。一般来说,使用invokeLater可以覆盖对话框的行为。

,

我想我知道示例中发生了什么。考虑了Dialog.show()代码(不容易考虑...)并被www上的一些OpenJavadoc提示后,我在package-private java.awt.EventDispatchThread类中找到了以下内容:

“线程通过调用来启动“永久”事件泵 pumpEvents(Conditional)在其run()方法中。事件处理程序可以选择 随时阻止此事件泵,但应启动一个新的泵(不 通过再次调用pumpEvents(Conditional)来创建新的EventDispatchThread)。 ...”

因此,正如上面引用的帖子所暗示的那样,“所有对话框都是模态...”的说法是正确的-从某种意义上说,但是当EDT是执行者时,线程实际上并不会“阻塞” 。每个JOptionPane都包装在一个JDialog中,该Jtor的ctor是EventDispatchThread文档中的“事件处理程序”。然后通过JDialog.show()实现该对话框。 JDialog.show()在关闭对话框之前不会返回,但是它也不会阻止EDT-相反,它对焦点系统和事件过滤器产生了魔力,从而产生了模态效果-并立即启动了一个新的事件泵(在同一EDT上执行)在EventDispatchThread文档中指示与“事件处理程序”一起执行。当用户撤消JOP时,所有这些都将解开。

在我的示例中,调用者不是EDT,因此,根据需要,invokeLater()忠实地将所有JOP ctor发布到系统事件队列中。正在运行的事件泵将处理第一个showMessageDialog()并不会返回,但会在EDT上启动一个新的事件泵,该事件泵将立即处理第二个showMessageDialog(),将其转换为一个JDialog,该JDialog不会返回但会启动一个新事件无限次加注EDT。每次JDialog发挥其焦点系统魔术作用,这意味着显示的最后一个对话框是GUI中唯一可访问的对话框-这种焦点技巧使Swing“出现”在等待最后一个JOP-实际上,EDT仍在继续愉快地相处。一定是这样的,否则关闭JOP的GUI输入将永远不会被处理,并且实际上会挂起。

此行为说明了为什么(例如)您在Javadoc中找到javax.swing.SwingWorker.get()的原因:

“当您希望SwingWorker在事件调度线程上阻塞时 我们建议您使用模式对话框。”

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

为了在非EDT继续工作的同时获得OP中描述的所需FIFO行为,我选择了以下内容。欢迎提供有关可靠性或模式的建设性意见。

一些注意事项:

  • 为简洁起见,我不建议在ctor中启动守护程序线程。如果使用它,则应按照以下注释中的描述进行修改:https://stackoverflow.com/a/28929746/3006394
  • 使用固定容量队列。如果需要无限制的容量(最大为Integer.MAX_VALUE),则可以使用LinkedBlockingQueue。
  • 如果您的需求涉及关闭公告器,监视其进度,从每个JOptionPane交互中检索用户输入等功能,那么java.util.concurrent.Executors类可以提供比我在这里使用的简单线程。
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