简体   繁体   English

Java(和 SwingUtilities)中的 GUI 线程

[英]GUI Threading in Java (and SwingUtilities)

I'm making a simple game in Java using swing and am having problems with my GUI freezing up sporadically (due to threading issues most probably) after a button press that is supposed to trigger a switch in JPanels.我正在使用 Swing 在 Java 中制作一个简单的游戏,并且在按下应该触发 JPanels 中的开关的按钮后,我的 GUI 偶尔会冻结(最有可能是由于线程问题)。

I posted a related thread here , which has more details about the actual code I'm currently using (although I did update the countdown and get that working fine).我在这里发布了一个相关线程,其中包含有关我当前正在使用的实际代码的更多详细信息(尽管我确实更新了倒计时并使其正常工作)。 From answers to that thread, it seems like using SwingUtilities.invokeLater() or invokeAndWait() might be what I need to solve the problem, but I'm not sure where in my code it is necessary or exactly how to implement it.从对该线程的回答来看,似乎使用SwingUtilities.invokeLater()invokeAndWait()可能是我需要解决的问题,但我不确定在我的代码中的哪个位置是必要的,或者具体如何实现它。

I don't know that much about threading and could use any help (preferably somewhat detailed and with some sample code) that I can get.我不太了解线程,可以使用我可以获得的任何帮助(最好是详细的并带有一些示例代码)。 Let me know if any further details would be useful.如果任何进一步的细节有用,请告诉我。

See: Tutorial: Concurrency in Swing请参阅: 教程:Swing 中的并发

Generally speaking, the Event Dispatch Thread is a single thread, chugging through the event queue, processing one at a time.一般来说,Eve​​nt Dispatch Thread 是一个单线程,通过事件队列,一次处理一个。

SwingUtilities.invokeLater(..) 

puts a Runnable on this queue.在这个队列上放置一个 Runnable。 So it will be processed by the EDT when the EDT finishes everything on the queue before it (This is why sleeping on the queue blocks other events like repainting).因此,当 EDT 在它之前完成队列上的所有内容时,它将被 EDT 处理(这就是为什么在队列上休眠会阻止其他事件,例如重绘)。 It's relatively unusual to call invokeLater(..) from the EDT itself, though there are situations where it is useful (usually as a hack).从 EDT 本身调用 invokeLater(..) 是相对不寻常的,尽管在某些情况下它很有用(通常作为一个 hack)。 I don't think I have had a legitimate use for SwingUtilities.invokeAndWait(..) in the last 6 years.我认为在过去的 6 年里我没有合法使用 SwingUtilities.invokeAndWait(..)。 Maybe once.也许一次。

javax.swing.Timer can be configured to fire once or periodically. javax.swing.Timer可以配置为触发一次或定期触发。 When it fires, it puts an event on the EDT queue.当它触发时,它会在 EDT 队列中放置一个事件。 If you have computationally-intensive processing that need to be done, consider using javax.swing.SwingWorker to do the computation on another thread, and give you back the result in a thread-safe manner (this is also comparatively rare).如果您有需要进行的计算密集型处理,请考虑使用javax.swing.SwingWorker在另一个线程上进行计算,并以线程安全的方式返回结果(这种情况也比较少见)。

I have create a WorkerThread class which take care of Threads and GUI current/main thread .我创建了一个 WorkerThread 类,它负责处理线程和 GUI 当前/主线程。 i have put my GUI application in construct() method of WorkerThread when an event fire to start XXXServer then all threads are activate and GUI work smoothlly wihout freeze.当事件触发以启动 XXXServer 时,我已将我的 GUI 应用程序放入 WorkerThread 的construct() 方法中,然后所有线程都被激活并且GUI 可以顺利工作而不会冻结。 have a look.看一看。 /** * Action Event * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed(ActionEvent ae) { log.info("actionPerformed begin..." + ae.getActionCommand()); /** * 动作事件 * * @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());
    }

}

In order to invoke an action in the existing WorkerThread, one would intuitively send a user defined event using SwingUtilities.invokeLater() to a JFrame's actionPerformed() method as为了调用现有 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)
    {
        ...
    }

}

Now, TestFrame.invokeLater() called in any Thread will be processed in TestFrame.actionPerformed() in existing WorkerThread .现在,在任何线程中调用的 TestFrame.invokeLater() 将在现有 WorkerThread 中的 TestFrame.actionPerformed() 中处理。

A good point to look is the docs .一个很好的看点是docs In your case, this explains how SwingUtilities.invokeLater() works and where to use it:在您的情况下,这解释了SwingUtilities.invokeLater()工作原理以及在何处使用它:

Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread.导致 doRun.run() 在 AWT 事件调度线程上异步执行。 This method should be used when an application thread needs to update the GUI .当应用程序线程需要更新 GUI 时,应使用此方法

So, in your actions that modifies the GUI you must use the invokeLater method to assure that the GUI wont freeze.因此,在修改 GUI 的操作中,您必须使用invokeLater方法来确保 GUI 不会冻结。

Another good resource is the Java tutorials.另一个很好的资源是 Java 教程。 They cover concurrency in Swing .它们涵盖了 Swing 中的并发性

If you have some work defined in your GUI code like this如果您在 GUI 代码中定义了一些像这样的工作

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

And you are calling it by attaching it to a new Thread你通过将它附加到一个新Thread来调用它

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

You are executing your work in GUI thread, which will cause problems in a Swing application.您正在 GUI 线程中执行您的工作,这将导致 Swing 应用程序出现问题。

Instead try this (let me mention this is just an example of usage)而是尝试这个(让我提一下这只是一个用法示例)

SwingUtilities.invokeLater(doWorkRunnable);

This puts your Runnable worker to the AWT Event Queue, and will execute it when previous events are finished.这会将您的Runnable工作Runnable放入 AWT 事件队列,并在之前的事件完成时执行它。

EDIT : Here's the complete example which executes countdown from 3 to 0 and then does whatever you want to do after countdown.编辑:这是一个完整的例子,它执行从 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