为什么JavaFX需要Platform.runLater来更改线程内UI中的任何内容?



这个简单摆动应用程序的代码:

final JFrame jFrame = new JFrame();
final JLabel jLabel = new JLabel("Test");
jFrame.add(jLabel);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.pack();
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
Thread thread = new Thread(){
    @Override
    public void run() {
        while(true) {
            jLabel.setText("Time: " + System.currentTimeMillis());
            try { Thread.sleep(1000);} catch(Exception ex) {}
        }
    }
};
thread.setDaemon(true);
thread.start();

按预期运行。如果我们对 JavaFX 执行相同的操作,则在将文本设置为标签时java.lang.IllegalStateException: Not on FX application thread会出现错误。

我只想知道,为什么一定要这样?为什么我们不能在我们想做的时候自由地做我们抱怨的事情?

因为JavaFX是单线程的,就像几乎所有的UI工具包一样。

您的 Swing 代码已损坏 - Swing 也是单线程的,您应该在 AWT 事件处理线程上执行jLabel.setText(...),方法是将其包装在 SwingUtilities.runLater(...); 中,或使用更高级别的 API(如 Timer)。它可能碰巧在您的特定系统上运行,使用您的特定 JDK 实现,但您的编码与您正在使用的工具包的合约相反,并且不能保证它会在其他 JDK 实现或其他平台上工作。

JavaFX 相对于 Swing 的一个主要改进是,JavaFX 通过当您遇到线程错误时抛出IllegalStateException来尽可能多地执行线程规则。在 Swing 中,您的代码在将来的某个时候容易发生随机的、不可预测的故障,而没有任何警告。

在某些情况下,JavaFX 不强制执行规则,大概是因为它会对执行相关检查的性能产生不利影响,但您仍应遵循约定:对 UI 的所有更改都必须在 UI 线程上执行。

至于为什么JavaFX,Swing和其他UI工具包是这样编写的:这是一个非常复杂的问题。在大多数情况下,UI 编程是事件驱动的;所以大多数时候你写的修改场景图的代码是为了响应用户事件;即 UI 线程无论如何都是修改 UI 的自然位置。因此,使 UI 工具包线程安全所带来的额外努力和性能影响可能不值得。尽管如此,已经进行了一些尝试来编写线程安全的 UI 工具包,但它们从未很好地工作过:增加的同步负担会对性能产生负面影响,以至于它们无法使用。这超出了我的理解范围,但从我所读到的内容来看,问题是布局通常是在与事件处理相反的方向上完成的(即从窗口递归向下到子组件,而不是从"叶子"组件通过父组件向上到窗口)。因此,虽然处理布局或事件处理的同步是可行的,但为两者执行此操作的成本过高。请参阅此内容以进行(可能更准确)讨论。

最新更新