【问题标题】:java updating UI components from another threadjava从另一个线程更新UI组件
【发布时间】:2014-12-16 09:54:54
【问题描述】:

我找到了很多关于我的问题的答案,但我仍然不明白为什么我的应用程序没有抛出任何异常。 我在 NetBeans 8 中创建了一个新的 java 表单应用程序。 我的表单是这样创建并显示在 main 方法中的:

public static void main(String args[])
    {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try
        {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels())
            {
                if ("Nimbus".equals(info.getName()))
                {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        }
        catch (ClassNotFoundException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (InstantiationException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (IllegalAccessException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (javax.swing.UnsupportedLookAndFeelException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                new MainForm().setVisible(true);     
            }
        });
    }

因此,这个新的 Runnable 创建了新的 MainForm 并将其设置为可见。

然后,在我的代码中,我启动了更新一些 jButton 和 jTextField 的新线程。代码如下:

private void updateUI() {
        updateUIThread = new Thread(() ->
        { 
            while (true) {
                try {
                    jtfIP.setEnabled(!Start && !autoRec);
                    jtfPort.setEnabled(!Start && !autoRec);
                    jtfSlaveID.setEnabled(!Start && !autoRec);
                    jtfTimeout.setEnabled(!Start && !autoRec);
                    jtfReqInterval.setEnabled(!Start && !autoRec);
                    jCheckBox1.setEnabled(!Start && !autoRec);
                    jCBReconnect.setEnabled(!Start && !autoRec);

                    if (db != null) {
                        if (!db.getIsOpen()) {
                            jPBD.setBackground(Color.RED);
                            jPBD.setForeground(Color.WHITE);
                            jPBD.setText("ER");
                        } else {
                            jPBD.setBackground(Color.GREEN);
                            jPBD.setForeground(Color.BLACK);
                            jPBD.setText("OK ");
                        }
                    } else {
                        jPBD.setBackground(Color.RED);
                        jPBD.setForeground(Color.WHITE);
                        jPBD.setText(" ER ");
                    }


                    if (autoRec){
                        jbtnConnect.setText("Auto");
                        if (Start && Connected) {
                            jbtnConnect.setForeground(Color.BLACK);
                            jbtnConnect.setBackground(Color.GREEN);
                        } else {       
                            jbtnConnect.setForeground(Color.WHITE);
                            jbtnConnect.setBackground(Color.RED);
                        }
                    } else {
                        if (Start) {
                            jbtnConnect.setText("Disconnect");
                            jbtnConnect.setForeground(Color.BLACK);
                            jbtnConnect.setBackground(Color.GREEN);

                        } else {
                            jbtnConnect.setText("Connect");
                            jbtnConnect.setForeground(Color.WHITE);
                            jbtnConnect.setBackground(Color.RED);
                        }
                    }

                    jtfErroriCitire.setText(String.valueOf(totalErrors));

                    try
                    {
                        Thread.sleep(300);
                        jPanel4.repaint(1);
                    }
                    catch (InterruptedException ex)
                    {
                        Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                catch (Exception ex) {
                    Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        updateUIThread.start();
    }

还有其他像上面这样启动的线程,我得到不同的值,这些值在上面的线程中更新。

我的问题是为什么我的代码不会引发任何关于从另一个线程更新的 UI 元素的异常?我没有使用SwingUtilities.invokeLater(new Runnable() { //code here }); 而且我的代码执行得很完美……

谢谢!

【问题讨论】:

  • 因为他们不这样做,所以由开发人员确保他们正确地将更新与 EDT 同步。做出该决定的原因很可能部分是由于这种设计的复杂性,部分是由于“可能”改变状态的组件的每种方法都会产生的费用必须检查是否正确线程
  • 好的,所以我的代码应该使用 SwingUtilities.invokeLater(new Runnable() {});并且我的方法 UpdateUI() 应该在 invokeLater(new Runnable() { updateUI();} 中调用,或者我的方法应该在我的线程中调用 invokeLater,例如: private void updateUI() { updateUIThread = new Thread(() - > { while (true) { try { SwingUtilities.invokeLater(new Runnable() //code here });
  • 当您想要更新 UI 时,您应该在 EDT 的上下文中执行此操作。一个好的方法是在 EDT 中使用 SwingWorkerpublish 内容为 processed 或触发属性更改事件...

标签: java multithreading user-interface elements invokelater


【解决方案1】:

Swing 不是线程安全的,是单线程的。您不应该从事件调度线程之外更新 UI 组件,同样,您也不应该在 EDT 中运行长时间运行的进程或阻塞代码,因为这将阻止它处理事件队列中的新事件,导致您的应用看起来像挂了……因为它有……

查看Concurrency in Swing了解更多详情。

挠了一阵子后,我意识到,简单的解决方案就是使用javax.swing.Timer

您想定期(300 毫秒)重复更新并更新 UI,完美,Swing Timer 能够定期安排更新并在 EDT 的上下文中执行回调!

它还具有合并重复呼叫的能力。这意味着,如果事件队列中已经有一个“计时器”动作,计时器将不会生成新的动作,从而防止 EDT 泛滥并导致可能的性能问题......

javax.swing.Timer timer = new Timer(300, new ActionListener() {
    public void actionPerformed(ActionEvent evt) {    
        jtfIP.setEnabled(!Start && !autoRec);
        jtfPort.setEnabled(!Start && !autoRec);
        jtfSlaveID.setEnabled(!Start && !autoRec);
        jtfTimeout.setEnabled(!Start && !autoRec);
        jtfReqInterval.setEnabled(!Start && !autoRec);
        jCheckBox1.setEnabled(!Start && !autoRec);
        jCBReconnect.setEnabled(!Start && !autoRec);

        if (db != null) {
            if (!db.getIsOpen()) {
                jPBD.setBackground(Color.RED);
                jPBD.setForeground(Color.WHITE);
                jPBD.setText("ER");
            } else {
                jPBD.setBackground(Color.GREEN);
                jPBD.setForeground(Color.BLACK);
                jPBD.setText("OK ");
            }
        } else {
            jPBD.setBackground(Color.RED);
            jPBD.setForeground(Color.WHITE);
            jPBD.setText(" ER ");
        }


        if (autoRec){
            jbtnConnect.setText("Auto");
            if (Start && Connected) {
                jbtnConnect.setForeground(Color.BLACK);
                jbtnConnect.setBackground(Color.GREEN);
            } else {       
                jbtnConnect.setForeground(Color.WHITE);
                jbtnConnect.setBackground(Color.RED);
            }
        } else {
            if (Start) {
                jbtnConnect.setText("Disconnect");
                jbtnConnect.setForeground(Color.BLACK);
                jbtnConnect.setBackground(Color.GREEN);

            } else {
                jbtnConnect.setText("Connect");
                jbtnConnect.setForeground(Color.WHITE);
                jbtnConnect.setBackground(Color.RED);
            }
        }

        jtfErroriCitire.setText(String.valueOf(totalErrors));
    }
});
timer.start();

详情请见How to use Swing Timers

【讨论】:

  • 感谢@MadProgrammer。我读过关于 Swing Timers 但没有使用它们。我以前在 c# 中使用的那种方法,我是 java 新手,我发现它与 c# 有很大不同。
  • @serban.b 这不是真的吗:P
  • @MadProgrammer 如果你还在的话:这对我不起作用。任务按延迟正确运行,但仍无法更新 gui 元素
  • @Blaine 适合我
  • @MadProgrammer 嗯...也可以在简化代码中为我工作。我的其他 700 行有些奇怪。奇怪...
【解决方案2】:

Swing 说您不应该从 Swing 事件调度线程之外更新组件,但它并没有强制执行。检查每个调用来自哪个线程根本不切实际。

此外,由于线程问题(只是一般情况下)往往会导致问题的性质,当您在多线程代码中出现错误时,您不应该期望总是抛出异常。这是因为线程问题经常导致deadlockmemory consistency errors,在大多数情况下无法恢复(通常整个 JVM 只是崩溃)。

【讨论】:

  • 谢谢,现在很清楚了。我读到了 SwingWorker(@MadProgrammer 所说的),我发现这是正确的实现,但我发现用SwingUtilities.invokeLater(new Runnable() public void run() {//updateUI(); } }); 来做是可以的,我认为这比 SwingWorker 容易得多,但我真的不是知道使用它是否 100% 安全或正确。
  • 是的,使用SwingUtilities.invokeLater(Runnable) 总是安全的。不过,SwingWorkers 有点不同。 invokeLater 允许您在 Swing EDT 上运行代码,而 SwingWroker 为您提供了一种在单独的后台线程上运行代码的方法,然后在后台线程完成时从 swingWroker 的 done 方法更新 UI。
  • @serban.b SwingWorker 在您需要更改不同组件的多个状态时变得更加有用,因为它提供了简单易用的方法,不仅可以将更新同步回 EDT(通过诸如publish/process) 还可以通过 PropertyChangeListener 支持在工作人员和 EDT 之间传递信息...尝试使用 SwingUtilities 传递变量...
【解决方案3】:

我做到了。

private void updateUI() {
    updateUIThread = new Thread(() ->
    { 
        while (true) {
            try {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        jtfIP.setEnabled(!Start && !autoRec);
                        jtfPort.setEnabled(!Start && !autoRec);
                        jtfSlaveID.setEnabled(!Start && !autoRec);
                        jtfTimeout.setEnabled(!Start && !autoRec);
                        jtfReqInterval.setEnabled(!Start && !autoRec);
                        jCheckBox1.setEnabled(!Start && !autoRec);
                        jCBReconnect.setEnabled(!Start && !autoRec);

                        if (db != null) {
                            if (!db.getIsOpen()) {
                                jPBD.setBackground(Color.RED);
                                jPBD.setForeground(Color.WHITE);
                                jPBD.setText("ER");
                            } else {
                                jPBD.setBackground(Color.GREEN);
                                jPBD.setForeground(Color.BLACK);
                                jPBD.setText("OK ");
                            }
                        } else {
                            jPBD.setBackground(Color.RED);
                            jPBD.setForeground(Color.WHITE);
                            jPBD.setText(" ER ");
                        }


                        if (autoRec){
                            jbtnConnect.setText("Auto");
                            if (Start && Connected) {
                                jbtnConnect.setForeground(Color.BLACK);
                                jbtnConnect.setBackground(Color.GREEN);
                            } else {       
                                jbtnConnect.setForeground(Color.WHITE);
                                jbtnConnect.setBackground(Color.RED);
                            }
                        } else {
                            if (Start) {
                                jbtnConnect.setText("Disconnect");
                                jbtnConnect.setForeground(Color.BLACK);
                                jbtnConnect.setBackground(Color.GREEN);

                            } else {
                                jbtnConnect.setText("Connect");
                                jbtnConnect.setForeground(Color.WHITE);
                                jbtnConnect.setBackground(Color.RED);
                            }
                        }

                        jtfErroriCitire.setText(String.valueOf(totalErrors));
                    }
                });
                try
                {
                    Thread.sleep(300);
                    jPanel4.repaint(1);
                }
                catch (InterruptedException ex)
                {
                    Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            catch (Exception ex) {
                Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    });
    updateUIThread.start();
}

我将我的更新 UI 代码放在了 invokeLater 的运行方法中。 updateUI() 它在应用程序启动时调用。

【讨论】:

  • 我会小心这种方法,因为它有可能淹没 EDT 请求,导致应用程序性能下降
  • 它造成了一些问题...我有 5-12% 的处理器使用率(在 TaskManager 中,在 Windows 7 64 位下)和类似 125-155 mb 的内存使用量。我将尝试使用 Swing Timers 并在几天后发布结果,届时我将出差回来。最好的问候
  • “用这种方法”,不一定用这个例子。 “这种方法”“可以”用更新淹没 EDT,特别是如果这些更新足够快或足够多
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多