事件派发线程如何工作?

时间:2010-03-20 19:34:44

标签: java user-interface events multithreading invoke

stackoverflow上的人的帮助下,我能够得到以下简单GUI倒计时的工作代码(它只显示一个倒数秒的窗口)。我对此代码的主要问题是invokeLater内容。

据我了解invokeLater,它向事件调度线程(EDT)发送任务,然后EDT在“可以”(无论这意味着什么)时执行此任务。 是吗?

根据我的理解,代码的工作原理如下:

  1. main方法中,我们使用invokeLater来显示窗口(showGUI方法)。换句话说,显示窗口的代码将在EDT中执行。

  2. main方法中,我们也启动counter,并且计数器(通过构造)在另一个线程中执行(因此它不在事件调度线程中)。右

  3. counter在一个单独的线程中执行,并定期调用updateGUIupdateGUI应该更新GUI。 GUI正在EDT中运行。因此,updateGUI也应该在EDT中执行。这就是updateGUI的代码包含在invokeLater中的原因。是吗?

  4. 我不清楚为什么我们从美国东部时间拨打counter。无论如何,它不是在EDT中执行的。它立即启动,一个新线程并在那里执行counter。那么,为什么我们不能在counter阻止后调用main方法中的invokeLater

    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.SwingUtilities;
    
    public class CountdownNew {
    
        static JLabel label;
    
        // Method which defines the appearance of the window.   
        public static void showGUI() {
            JFrame frame = new JFrame("Simple Countdown");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            label = new JLabel("Some Text");
            frame.add(label);
            frame.pack();
            frame.setVisible(true);
        }
    
        // Define a new thread in which the countdown is counting down.
        public static Thread counter = new Thread() {
            public void run() {
                for (int i=10; i>0; i=i-1) {
                    updateGUI(i,label);
                    try {Thread.sleep(1000);} catch(InterruptedException e) {};
                }
            }
        };
    
        // A method which updates GUI (sets a new value of JLabel).
        private static void updateGUI(final int i, final JLabel label) {
            SwingUtilities.invokeLater( 
                new Runnable() {
                    public void run() {
                        label.setText("You have " + i + " seconds.");
                    }
                }
            );
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    showGUI();
                    counter.start();
                }
            });
        }
    
    }
    

4 个答案:

答案 0 :(得分:16)

如果我理解你的问题,你会想知道为什么你不能这样做:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
        }
    });
    counter.start();
}

你不能这样做的原因是因为调度程序不能保证...只是因为你调用了showGUI()然后调用counter.start()并不意味着showGUI()中的代码1}}将在counter

的run方法中的代码之前执行

这样想:

  • invokeLater 启动一个线程,该线程在EDT上安排一个异步事件,该事件的任务是创建 JLabel
  • 计数器是一个单独的线程,依赖于JLabel存在,因此它可以调用label.setText("You have " + i + " seconds.");

现在你有一个竞争条件: JLabel必须在counter线程开始之前创建,如果它不是在计数器线程启动之前创建的,那么你的计数器线程将会在未初始化的对象上调用setText

为了确保消除竞争条件,我们必须保证执行顺序和单向,以确保它在顺序上执行showGUI()counter.start()同一个主题:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
            counter.start();
        }
    });
}

现在showGUI();counter.start();从同一个线程执行,因此JLabel将在counter启动之前创建。

更新:

  

问: 我不明白这个帖子有什么特别之处。
   A: Swing事件处理代码在称为事件派发线程的特殊线程上运行。大多数调用Swing方法的代码也在这个线程上运行。这是必要的,因为大多数Swing对象方法都不是“线程安全的”:从多个线程调用它们会冒线程干扰或内存一致性错误。 1

     

问: 那么,如果我们有一个GUI,为什么要在一个单独的线程中启动它?
  答:可能有一个比我更好的答案,但如果你想从EDT更新GUI(你这样做),那么你必须从EDT开始。

     

问: 为什么我们不能像其他线程一样启动线程?
   A:请参阅上一个答案。

     

问: 为什么我们使用一些invokeLater以及为什么这个线程(EDT)在准备就绪时开始执行请求。为什么它不总是准备好?
   A: EDT可能还有其他一些需要处理的AWT事件。   invokeLater使得doRun.run()在AWT事件派发线程上异步执行。这将在处理完所有挂起的AWT事件后发生。当应用程序线程需要更新GUI时,应使用此方法。 2

答案 1 :(得分:2)

您实际上从EDT开始 counter主题。如果您在counter.start()阻止后调用invokeLater,则计数器可能会在 GUI变为可见之前开始运行。现在,因为您正在EDT中构建GUI,所以当counter开始更新时,GUI将不会存在。幸运的是,您似乎将GUI更新转发到EDT,这是正确的,并且由于EventQueue是一个队列,因此第一次更新将在构建GUI之后发生,因此应该没有理由不这样做。但更新可能不可见的GUI有什么意义呢?

答案 2 :(得分:1)

  

什么是EDT?

围绕Swing API出现的大量并发问题,这是一个hacky解决方法;)

说真的,很多Swing组件都不是“线程安全的”(一些着名的程序员甚至称为Swing“线程敌对”)。通过拥有一个独特的线程,对这个线程恶意的组件进行所有更新,你就会避免很多潜在的并发问题。除此之外,您还可以保证它将按顺序使用Runnable运行您通过它的invokeLater

然后一些挑剔:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
            counter.start();
        }
    });
}

然后:

  

在主要方法中我们也开始了   柜台和柜台(由   建筑)在另一个执行   线程(所以它不在事件中   调度线程)。正确?

你并没有真正在主方法中启动计数器。您在EDT上执行的匿名Runnable run()方法中启动计数器。所以你真的从EDT开始计数器Thread,而不是主要方法。然后,因为它是一个单独的线程,它在EDT上不是运行。但是计数器肯定是 在EDT上启动,而不是Thread执行main(...)方法。

这是挑剔,但我认为这个问题仍然很重要。

答案 3 :(得分:0)

这很简单,如下

第1步。初始线程也称为主线程。

步骤2.创建一个可运行的对象并将其传递给invokeLate()。

步骤3.这会初始化GUI但不会创建GUI。

步骤4. InvokeLater()调度创建的对象以便在EDT上执行。

步骤5.已创建GUI。

步骤6.所有发生的事件都将放在EDT中。