繁体   English   中英

Java(和 SwingUtilities)中的 GUI 线程

[英]GUI Threading in Java (and SwingUtilities)

我正在使用 Swing 在 Java 中制作一个简单的游戏,并且在按下应该触发 JPanels 中的开关的按钮后,我的 GUI 偶尔会冻结(最有可能是由于线程问题)。

我在这里发布了一个相关线程,其中包含有关我当前正在使用的实际代码的更多详细信息(尽管我确实更新了倒计时并使其正常工作)。 从对该线程的回答来看,似乎使用SwingUtilities.invokeLater()invokeAndWait()可能是我需要解决的问题,但我不确定在我的代码中的哪个位置是必要的,或者具体如何实现它。

我不太了解线程,可以使用我可以获得的任何帮助(最好是详细的并带有一些示例代码)。 如果任何进一步的细节有用,请告诉我。

请参阅: 教程:Swing 中的并发

一般来说,Eve​​nt Dispatch Thread 是一个单线程,通过事件队列,一次处理一个。

SwingUtilities.invokeLater(..) 

在这个队列上放置一个 Runnable。 因此,当 EDT 在它之前完成队列上的所有内容时,它将被 EDT 处理(这就是为什么在队列上休眠会阻止其他事件,例如重绘)。 从 EDT 本身调用 invokeLater(..) 是相对不寻常的,尽管在某些情况下它很有用(通常作为一个 hack)。 我认为在过去的 6 年里我没有合法使用 SwingUtilities.invokeAndWait(..)。 也许一次。

javax.swing.Timer可以配置为触发一次或定期触发。 当它触发时,它会在 EDT 队列中放置一个事件。 如果您有需要进行的计算密集型处理,请考虑使用javax.swing.SwingWorker在另一个线程上进行计算,并以线程安全的方式返回结果(这种情况也比较少见)。

我创建了一个 WorkerThread 类,它负责处理线程和 GUI 当前/主线程。 当事件触发以启动 XXXServer 时,我已将我的 GUI 应用程序放入 WorkerThread 的construct() 方法中,然后所有线程都被激活并且GUI 可以顺利工作而不会冻结。 看一看。 /** * 动作事件 * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed(ActionEvent ae) { log.info("actionPerformed begin..." + ae.getActionCommand());

    try {
        if (ae.getActionCommand().equals(btnStart.getText())) {
             final int portNumber = 9990;
             try {

                 WorkerThread workerThread = new WorkerThread(){
                    public Object construct(){

                        log.info("Initializing the XXXServer ...");
                        // initializing the Socket Server
                         try {
                            XXXServer xxxServer = new XXXServer(portNumber);
                            xxxServer.start();
                            btnStart.setEnabled(false);                             
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            log.info("actionPerformed() Start button ERROR IOEXCEPTION..." + e.getMessage());
                            e.printStackTrace();
                        }
                        return null;
                    }
                };workerThread.start();
                } catch (Exception e) {
                    log.info("actionPerformed() Start button ERROR..." + e.getMessage());
                    e.printStackTrace();
             }


        } else if (ae.getActionCommand().equals(btnStop.getText())) {
            log.info("Exit..." + btnStop.getText());
            closeWindow();
        }

    } catch (Exception e) {
        log
            .info("Error in ServerGUI actionPerformed==="
                + e.getMessage());
    }

}

为了调用现有 WorkerThread 中的操作,可以直观地使用 SwingUtilities.invokeLater() 将用户定义的事件发送到 JFrame 的 actionPerformed() 方法,如

class TestFrame extends JFrame implements ActionListener
{
...

    private class Performer implements Runnable
    {
        ActionEvent event;

        Performer(ActionEvent event)
        {
            this.event = event;
        }

        @Override
        public void run()
        {
            actionPerformed(event);
        }

    }

    synchronized protected void invokeLater(ActionEvent event)
    {
        SwingUtilities.invokeLater(new Performer(event));
    }

    public void actionPerformed(ActionEvent event)
    {
        ...
    }

}

现在,在任何线程中调用的 TestFrame.invokeLater() 将在现有 WorkerThread 中的 TestFrame.actionPerformed() 中处理。

一个很好的看点是docs 在您的情况下,这解释了SwingUtilities.invokeLater()工作原理以及在何处使用它:

导致 doRun.run() 在 AWT 事件调度线程上异步执行。 当应用程序线程需要更新 GUI 时,应使用此方法

因此,在修改 GUI 的操作中,您必须使用invokeLater方法来确保 GUI 不会冻结。

另一个很好的资源是 Java 教程。 它们涵盖了 Swing 中的并发性

如果您在 GUI 代码中定义了一些像这样的工作

Runnable doWorkRunnable = new Runnable() {
    @Override
    public void run() { 
        doWork(); 
    }
};

你通过将它附加到一个新Thread来调用它

Thread t = new Thread(doWorkRunnable);
t.start();

您正在 GUI 线程中执行您的工作,这将导致 Swing 应用程序出现问题。

而是尝试这个(让我提一下这只是一个用法示例)

SwingUtilities.invokeLater(doWorkRunnable);

这会将您的Runnable工作Runnable放入 AWT 事件队列,并在之前的事件完成时执行它。

编辑:这是一个完整的例子,它执行从 3 到 0 的倒计时,然后在倒计时后做任何你想做的事情。

public class TestFrame extends JFrame {

    private JPanel contentPane;
    private final Timer timer;
    private TimerTask[] tasks;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TestFrame frame = new TestFrame();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public TestFrame() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        final JLabel lblCountdown = new JLabel();
        contentPane.add(lblCountdown, BorderLayout.NORTH);
        JButton btnStart = new JButton("Start");
        contentPane.add(btnStart, BorderLayout.SOUTH);

        timer = new Timer();
        tasks = new TimerTask[4];

        setContentPane(contentPane);

        for (int i = 0; i < 4; i++) {
            final int count = i;
            tasks[i] = new TimerTask() {
                public void run() {
                    EventQueue.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            lblCountdown.setText(count + "");
                        }
                    });
                }
            };
        }

        btnStart.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                for (int i = 0; i < 4; i++) {
                    timer.schedule(tasks[4 - i - 1], (1000 * i), (1000 * (i + 1)));
                }
                // add another timer.schedule(TimerTask)
                // to execute that "move to game screen" task
                TimerTask taskGotoGame = new TimerTask() {
                    public void run() {
                        timer.cancel();
                        JOptionPane.showMessageDialog(null, "Go to game", "Will now", JOptionPane.INFORMATION_MESSAGE);
                        System.exit(0);
                    }
                };
                // and schedule it to happen after ROUGHLY 3 seconds
                timer.schedule(taskGotoGame, 3000);
            }
        });

    }

}

暂无
暂无

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

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