简体   繁体   English

Java从另一个线程更新UI组件

[英]java updating UI components from another thread

I found many answers about my question, but I still don't understand why my application does not throw any exceptions. 我找到了有关该问题的许多答案,但我仍然不明白为什么我的应用程序不会引发任何异常。 I created a new java form application in NetBeans 8. My form is created and displayed in main method like this: 我在NetBeans 8中创建了一个新的Java表单应用程序。我的表单已创建并显示在以下主要方法中:

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

So, this new Runnable creates new MainForm and sets it visible. 因此,此新的Runnable将创建新的MainForm并将其设置为可见。

Then, in my code I start new threads which updates some jButtons and jTextFields. 然后,在我的代码中,我启动了新线程,这些线程更新了一些jButton和jTextField。 Code below: 代码如下:

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

And there are other threads started like this above and where I get different values which are updated in the above thread. 并且还有其他像以上这样启动的线程,在这里我得到了不同的值,这些值在上述线程中进行了更新。

My question is why my code does not throw any exception regarding UI elements which are updated from another thread? 我的问题是为什么我的代码不会对从另一个线程更新的UI元素引发任何异常? I did NOT use SwingUtilities.invokeLater(new Runnable() { //code here }); 我没有使用SwingUtilities.invokeLater(new Runnable() { //code here }); And my code executes perfectly... 而且我的代码执行得很好...

Thank you! 谢谢!

Swing is not thread safe and is single threaded. 摆动不是线程安全的,而是单线程的。 You should never update UI components from outside the Event Dispatching Thread, equally, you should never run long running processes or blocking code within the EDT, as this will prevent it from processing new events within the event queue, causing your app to look like it's hung...because it has... 同样,您永远都不要从事件调度线程外部更新UI组件,同样,您也绝不应该在EDT中运行长时间运行的进程或阻塞代码,因为这将阻止它处理事件队列中的新事件,从而使您的应用看起来像是挂...因为它有...

Take a look at Concurrency in Swing for more details. 有关更多详细信息,请参阅Swing中的并发

After scratching my head for a while, I realised, the simple solution would be to just use javax.swing.Timer 我意识到,挠了一下头之后,简单的解决方案是只使用javax.swing.Timer

You want to repeat the update at a regular interval (300 milliseconds) and update the UI, perfect, the Swing Timer is capable of scheduling updates at regular intervals and executes it call back within the context of the EDT! 您想以固定的间隔(300毫秒)重复更新并更新UI,完美的是,Swing Timer能够以固定的间隔安排更新并在EDT的上下文中执行回调!

It also has the ability to consolidate repeated calls. 它还具有合并重复呼叫的能力。 This means, if there is already a "timer" action in the event queue, the timer will not generate a new one, preventing from flooding the EDT and cause possible performance issues... 这意味着,如果事件队列中已经存在“计时器”操作,则计时器将不会生成新的计时器,从而防止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();

See How to use Swing Timers for more details 有关更多详细信息,请参见如何使用Swing计时器

Swing says you shouldn't update components from outside the Swing Event Dispatch Thread, but it doesn't enforce that. Swing表示您不应从Swing Event Dispatch Thread外部更新组件,但不会强制这样做。 It's just not at all practical to check to see which Thread every single call is coming from. 检查每个调用来自哪个线程根本不可行。

Also, because of the nature of the problems that tend to result from threading issues (just generally), you should not expect Exceptions to always be thrown when you have a bug in multi-threaded code. 另外,由于线程问题(通常是线程问题)导致的问题的性质,您不应该期望在多线程代码中出现错误时总是抛出异常。 This is because threading issues frequently result in deadlock or memory consistency errors , which are in most cases not recoverable (usually the whole JVM just crashes). 这是因为线程问题经常导致死锁内存一致性错误 ,这些错误在大多数情况下是不可恢复的(通常是整个JVM崩溃)。

And I did this. 而我做到了。

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

I put my update UI code in run method of invokeLater. 我将我的更新UI代码放在invokeLater的run方法中。 updateUI() it's called when application starts. updateUI()在应用程序启动时调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM