事件调度线程如何工作?

发布于 2024-08-26 03:23:21 字数 2279 浏览 2 评论 0原文

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

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

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

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

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

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

我不清楚为什么我们从 EDT 调用计数器。无论如何,它不是在 EDT 中执行的。它立即启动,一个新线程和计数器在那里执行。那么,为什么我们不能在 main 方法的 invokeLater 块之后调用 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();
            }
        });
    }

}

With the help of people on stackoverflow I was able to get the following working code of a simple GUI countdown (which just displays a window counting down seconds). My main problem with this code is the invokeLater stuff.

As far as I understand invokeLater, it sends a task to the event dispatching thread (EDT) and then the EDT executes this task whenever it "can" (whatever that means). Is that right?

To my understanding, the code works like this:

  1. In the main method we use invokeLater to show the window (showGUI method). In other words, the code displaying the window will be executed in the EDT.

  2. In the main method we also start the counter and the counter (by construction) is executed in another thread (so it is not in the event dispatching thread). Right?

  3. The counter is executed in a separate thread and periodically it calls updateGUI. updateGUI is supposed to update the GUI. And the GUI is working in the EDT. So, updateGUI should also be executed in the EDT. It is the reason why the code for the updateGUI is enclosed in invokeLater. Is that right?

What is not clear to me is why we call the counter from the EDT. Anyway, it is not executed in the EDT. It starts immediately, a new thread and the counter is executed there. So, why can we not call the counter in the main method after the invokeLater block?

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

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

阳光下的泡沫是彩色的 2024-09-02 03:23:21

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

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

你不能这样做的原因是因为调度程序不做任何保证......只是因为你调用了 showGUI() 然后调用 counter.start() 并不意味着 showGUI() 中的代码将在 run 方法中的代码之前执行计数器。

可以这样想:

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

现在你有一个竞争条件: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 将在计数器启动之前创建。

更新:

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

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

问: 为什么我们不能像其他线程一样启动该线程?
答:请参阅之前的回答。

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

If I understand your question correctly you're wonder why you can't do this:

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

The reason why you can't do it is because the scheduler makes no guarantees... just because you invoked showGUI() and then you invoked counter.start() doesn't mean that the code in showGUI() will be executed before the code in the run method of the counter.

Think of it this way:

  • invokeLater starts a thread and that thread is schedules an asynchronous event on the EDT which is tasked with creating the JLabel.
  • the counter is a separate thread that depends on the JLabel to exists so it can call label.setText("You have " + i + " seconds.");

Now you have a race condition: JLabel must be created BEFORE the counter thread starts, if it's not created before the counter thread starts, then your counter thread will be calling setText on an uninitialized object.

In order to ensure that the race condition is eliminated we must guarantee the order of execution and one way to guarantee it is to execute showGUI() and counter.start() sequentially on the same thread:

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

Now showGUI(); and counter.start(); are executed from the same thread, thus the JLabel will be created before the counter is started.

Update:

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

动听の歌 2024-09-02 03:23:21

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

You are actually starting the counter thread from the EDT. If you called counter.start() after the invokeLater block, the counter would likely start to run before the GUI becomes visible. Now, because you're constructing the GUI in the EDT, the GUI wouldn't exist when the counter starts to update it. Luckily you seem to be forwarding the GUI updates to the EDT, which is correct, and since the EventQueue is a queue, the first update will happen after the GUI has been constructed, so there should be no reason why this wouldn't work. But what's the point of updating a GUI that may not be visible yet?

Oo萌小芽oO 2024-09-02 03:23:21

什么是 EDT?

这是一个解决 Swing API 存在的大量并发问题的巧妙解决方法;)

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

然后是一些挑剔:

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

然后:

在main方法中我们还启动了
计数器和计数器(通过
构造)在另一个中执行
线程(所以它不在事件中
调度线程)。对吗?

您实际上并没有在主方法中启动计数器。您可以在 EDT 上执行的匿名 Runnablerun() 方法中启动计数器。因此,您实际上是从 EDT 启动计数器 Thread,而不是从 main 方法。然后,因为它是一个单独的线程,所以它不会在 EDT 上运行。但计数器肯定在 EDT 上启动,而不是在执行 main(...) 方法的 Thread 中启动。

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

What is the EDT?

It's a hacky workaround around the great many concurrency issues that the Swing API has ;)

Seriously, a lot of Swing components are not "thread safe" (some famous programmers went as far as calling Swing "thread hostile"). By having a unique thread where all updates are made to this thread-hostile components you're dodging a lot of potential concurrency issues. In addition to that, you're also guaranteed that it shall run the Runnable that you pass through it using invokeLater in a sequential order.

Then some nitpicking:

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

And then:

In the main method we also start the
counter and the counter (by
construction) is executed in another
thread (so it is not in the event
dispatching thread). Right?

You don't really start the counter in the main method. You start the counter in the run() method of the anonymous Runnable that is executed on the EDT. So you really start the counter Thread from the EDT, not the main method. Then, because it's a separate Thread, it is not run on the EDT. But the counter definitely is started on the EDT, not in the Thread executing the main(...) method.

It's nitpicking but still important seen the question I think.

幼儿园老大 2024-09-02 03:23:21

这个很简单,如下

步骤1。创建初始线程也称为主线程。

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

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

步骤 4. InvokeLater() 安排创建的对象在 EDT 上执行。

步骤 5. GUI 已创建。

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

This is simple, it is as follows

Step 1 . Initial thread also called main thread is created.

Step 2. Create a runnable object and pass it to invokeLate().

Step 3. This initialises the GUI but does not create the GUI.

Step 4. InvokeLater() schedules the created object for execution on EDT.

Step 5. GUI has been created.

Step 6. All events occurring will be placed in EDT.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文