java - 事件派发线程如何工作?

标签 java user-interface events multithreading invoke

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

据我所知,invokeLater 将任务发送到事件分派(dispatch)线程 (EDT),然后 EDT 在“可以”时执行此任务(无论这意味着什么)。 是这样吗?

据我了解,代码是这样工作的:

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

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

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

我不清楚的是为什么我们从 EDT 调用 counter。无论如何,它不在 EDT 中执行。它立即启动,一个新线程和 counter 在那里执行。那么,为什么我们不能在 invokeLater block 之后调用 main 方法中的 counter 呢?

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();
            }
        });
    }

}

最佳答案

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

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

你不能这样做的原因是因为调度程序没有保证......只是因为你调用了 showGUI() 然后你调用了 counter.start() 并不意味着 showGUI() 中的代码将在 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 将在之前创建计数器 启动。

更新:

Q: And I do not understand what is special about this thread.
A: Swing event handling code runs on a special thread known as the event dispatch thread. Most code that invokes Swing methods also runs on this thread. This is necessary because most Swing object methods are not "thread safe": invoking them from multiple threads risks thread interference or memory consistency errors. 1

Q: So, if we have a GUI why should we start it in a separate thread?
A: There is probably a better answer than mine, but if you want to update the GUI from the EDT (which you do), then you have to start it from the EDT.

Q: And why we cannot just start the thread like any other other thread?
A: See previous answer.

Q: Why we use some invokeLater and why this thread (EDT) start to execute request when it's ready. Why it is not always ready?
A: The EDT might have some other AWT events it has to process. invokeLater Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread. This will happen after all pending AWT events have been processed. This method should be used when an application thread needs to update the GUI. 2

关于java - 事件派发线程如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2484425/

相关文章:

java - 如何从包含多个问题和选项以及信息的文本文件中读取单个问题?

javascript - 使用 Polymer 1.0 向用户提供操作反馈的最佳方式

javascript - 如何用异步代码实现顺序迭代

java - 动态添加 JTextFields 后将其删除

java - Wicket:从包含 Panel 到引用模型的组件

java - 有关通过 PropertyChangeSupport 更新 Swing GUI 的设计问题

java - 我可以在不更改 JPanel 和 JFrame 大小的情况下更改字体类型吗?

javascript - knockout : How to pass data-bound input value back to function on click?

c# - Winforms DataBinding 与 PropertyChanged 事件排序问题

java - 打印 Mqtt 主题并选择一个订阅其中之一