簡體   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