【问题标题】:SwingUtilities.invokeLater takes a Runnable and runs it on the EDT?SwingUtilities.invokeLater 需要一个 Runnable 并在 EDT 上运行它?
【发布时间】:2014-04-14 07:42:00
【问题描述】:

我对@9​​87654325@ 的签名感到困惑。它需要一个Runnable 对象。是交给 Event Dispatch Thread 的这个 Runnable 对象吗?为什么我不能在EDTrun方法上直接调用createAndShowGUI(如果可以的话)?

我有read articles 关于EDTinvokeLater 的工作原理,但我对传递的Runnable 对象感到困惑。

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        createAndShowGUI();
    }
});

如果我在通话下方再次拨打 SwingUtilities.invokeLater 会发生什么?

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        createAndShowGUI();
    }
});
SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        doSomethingOnTopOfGUI();
    }
});

【问题讨论】:

  • 你有什么困惑?
  • 什么是EDT的运行方法?你在哪里看到的?在java中,传递“代码块”的唯一方法是在类中。 Runnable.run 方法中的“代码块”稍后会被调用。
  • 这个问题没有意义。您似乎掌握了基本思想:EDT 在循环中运行并执行一小段代码,主要是为了对事件做出反应。通过使用 invokeLater 你传递了一个 Runnable 并且 EDT 调用它的 run 方法。您询问“直接”方式-您可以更直接吗?您如何想象“直接” api?
  • EDT 不是您自己开始的线程。它会自动启动,然后您使用 invokeLater() 传递一个任务(Runnable)以在该线程中运行。
  • 阅读Producer/Consumer pattern。 EDT 就是这样的实现。任务从任何地方(其他线程和 EDT)发出到 EDT 的工作队列。 EDT 获取队列中的任务(一次一个)并运行它们。您无法将代码传递给 run 方法,因为它已经运行的 EDT 并且只有一个

标签: java multithreading swing awt event-dispatch-thread


【解决方案1】:

什么是新的 Runnable() {}?

new Runnable() {
    public void run() {
        createAndShowGUI();
    }
};

这声明了一个匿名类并实例化了它的一个新实例。基本上相当于这样:

class Creator implements Runnable {
    public void run() {
        createAndShowGUI();
    }
}

new Creator();

在 Java 8 lambda 之前,匿名类是一种进行函数式编程的方法。将 Runnable 传递给 invokeLater 就像传递一个函数,EDT 可以稍后随时执行它(通过调用 run)。

invokeLater 对 Runnable 做了什么?

最初,它所做的只是创建一个事件来包装它(InvocationEvent)并将它放在队列的末尾。

在对事件进行排队后,invokeLater 通知 EDT(如果它正在等待则唤醒)。 EDT 自己的 run 方法是一个处理事件的无限循环。它看起来像这样(非常解释):

public void run() {
    while(true) {
        EventQueue eq = getEventQueue();

        synchronized(eq) {
            while(eq.hasNextEvent()) {
                processOneEvent(eq.getNextEvent());
            }

            try {
                eq.wait();
            } catch(InterruptedException ie) {}
        }
    }
}

当 EDT 到达新的 InvocationEvent 时,它会在 Runnable 上调用 run,后者会调用 createAndShowGUI

因此,将 Runnable 传递给 invokeLaterinvokeAndWait 可以让您在 EDT 上运行您想要的任何代码。

run 多久调用一次?

在大多数情况下,立即或几乎立即。 EDT 反应灵敏。 'invokeLater' 的 'Later' 部分只是对我们的一个提示:

  • run 是异步执行的。调用invokeLater 的线程可能会在事件处理之前继续调用。
  • 可能会先处理其他事件。

当然,如果需要同步调用runinvokeAndWait 可以作为替代方案。 (不过需要注意的是,invokeAndWait 如果在新的 InvocationEvent 之前排队,也可能导致其他事件被处理。)

异步

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        System.out.println("hello");
    }
});
System.out.println("world");

这可能会打印

你好 世界

它可以打印

世界 你好

因为在处理事件之前(或什至同时)调用线程可能会或可能不会继续。这取决于系统的线程调度程序。

同步

SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
        System.out.println("hello");
    }
});
System.out.println("world");

这肯定会打印出来

你好 世界

因为调用线程将等待run 完成后才继续(除非抛出异常)。

invokeAndWait 使用等待和通知方案来实现这一点。创建一个监视器对象并将其提供给 InvocationEvent。 invokeAndWait 在发布事件后调用 wait,在 EDT 上完成 run 后,InvocationEvent 调用 notifyAll。等待和通知方案是无法在 EDT 上调用 invokeAndWait 的原因。 EDT 没有办法在一个事件的中间停下来处理另一个事件。

如果有多个事件排队,会发生什么?

EDT 一次处理一个事件,按照它们的排队顺序和优先级。一些事件也可以合并(常规的 InvocationEvent 不会)。大多数事件是正常的优先级。 PaintEvent(例如来自调用 repaint)通常具有低优先级,而某些系统事件具有较高优先级。

在您给出的具体示例中:

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        createAndShowGUI();
    }
});
SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        doSomethingElse();
    }
});

由于它们是相同类型的事件和相同的优先级,doSomethingElse 事件将在createAndShowGUI 事件完成后处理。

同样,如果这样做:

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                System.out.println("world");
            }
        });
        System.out.println("hello");
    }
});

那会打印出来

你好 世界

因为world Runnable 在hello Runnable 完成后运行。

【讨论】:

  • 所以一旦createAndShowGUI 被执行,与GUI 组件上的操作相关的事件会再次添加到这个队列中吗?但我们还没有指定 EDT 这样做。换句话说,当我创建一个JButton 时,我没有调用SwingUtilies.invokeLater 将其添加到容器中?
  • 是的,createAndShowGUI 中的任何内容现在都在 EDT 上运行。如果createAndShowGUI 中的任何操作最终使新事件排队,则在 InvocationEvent 中的 Runnable 完成后处理新事件。
  • 两个完全独立的GUI添加到同一个EDT不会互相串扰吧?
  • @eagertoLearn 串扰?我认为您需要阅读 Java 中的线程概念。您的问题应该缺乏对该主题的基础知识的理解。一旦您了解了线程的工作原理,您将能够更好地理解 Swing 中的多线程是如何工作的。
  • @eagertoLearn 我不确定你的意思。 EDT 一次只处理一个事件。
【解决方案2】:

您可以使用 any 线程中的SwingUtilities.invokeLater() 将要在 EDT 上运行的任务排队。这在某些情况下很有用,尤其是在多线程应用程序中,作为从非 GUI 线程更新 GUI 元素的便捷方式,以及从运行的 main() 启动应用程序(您的示例提示)时在主线程中,而不是 EDT。

例如:

// main is not executed in the EDT
public static void main (String[] args) {

    // create and initialize GUI in the EDT to avoid potential thread-safety issues.
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });

}

您不妨看看The Event Dispatch Thread 上的官方教程,它应该可以澄清您对什么是 EDT 以及您如何访问它的一些误解。

【讨论】:

    猜你喜欢
    • 2011-04-02
    • 2012-04-05
    • 2014-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多