Java Swing:如何从事件派发线程中唤醒主线程?

时间:2012-04-21 22:05:06

标签: java multithreading swing events event-dispatch-thread

我想让“主线程”(运行main()的线程启动)从按钮的ActionListener的actionPerformed()方法做一些工作,但我不知道如何实现这一点

更多背景信息:

我目前正在使用Swing(一种俄罗斯方块的风格)编写2D游戏 当应用程序启动时,会打开一个窗口,显示游戏的主菜单。 向用户展示了几种可能性,其中之一是通过按下“开始”按钮来启动游戏,这将导致游戏面板显示并触发游戏的主循环。

为了能够在两个面板(主菜单和游戏面板)之间切换,我使用的是CardLayout管理器,然后我可以通过调用show()来显示一个面板。
我的想法是,我希望我的开始按钮有一个看起来像这样的监听器:

public class StartListener implements ActionListener {
    StartListener() {}
    public void actionPerformed(ActionEvent e) {
        displayGamePanel();
        startGame();
    }
}

但是这不起作用,因为从事件派发线程调用了actionPerformed(),所以调用startGame()(触发主循环:游戏逻辑更新+ repaint()调用每一帧)阻止整个线程。

我现在处理此问题的方法是actionPerformed()只更改布尔标志值:public void actionPerformed(ActionEvent e) { startPushed = true; }
然后最终由主线程检查:

while (true) {
    while (!g.startPushed) {
        try { 
            Thread.sleep(100); 
        } catch (Exception e) {}
    }
    g.startPushed = false;
    g.startGame();
}

但我觉得这个解决方案非常不优雅。

我已经阅读了Concurrency in Swing课程,但我仍然感到困惑(我应该实现一个工作线程 - 是不是有点矫枉过正?)。我还没有做过任何实际的多线程工作,所以我有点失落。

是不是有办法告诉主线程(哪个会无限期地睡觉,等待用户操作)“好的,现在醒来并执行此操作(显示游戏面板并开始游戏)”?< / p>

感谢您的帮助。

修改 为了清楚起见,这就是我的游戏循环:

long lastLoopTime = System.currentTimeMillis();
long dTime;
int delay = 10;
while (running) {
    // compute the time that has gone since the last frame
    dTime = System.currentTimeMillis() - lastLoopTime;
lastLoopTime = System.currentTimeMillis();

    // UPDATE STATE
    updateState(dTime);
    //...

    // UPDATE GRAPHICS
    // thread-safe: repaint() will run on the EDT
    frame.repaint()

    // Pause for a bit
    try { 
    Thread.sleep(delay); 
    } catch (Exception e) {}
}

5 个答案:

答案 0 :(得分:4)

这没有意义:

  

但是这不起作用,因为从事件派发线程调用了actionPerformed(),所以对startGame()的调用(触发主循环:每帧的游戏逻辑更新+ repaint()调用)会阻止整个线程。

由于你的游戏循环不应该阻止EDT。您是否在游戏循环中使用Swing Timer或后台线程?如果没有,请这样做。

关于:

while (true) {
    while (!g.startPushed) {
        try { 
            Thread.sleep(100); 
        } catch (Exception e) {}
    }
    g.startPushed = false;
    g.startGame();
}

也不要这样做,而是使用侦听器来做这种事情。

如,

import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

public class GameState extends JPanel {
   private CardLayout cardlayout = new CardLayout();
   private GamePanel gamePanel = new GamePanel();
   private StartPanel startpanel = new StartPanel(this, gamePanel);

   public GameState() {
      setLayout(cardlayout);
      add(startpanel, StartPanel.DISPLAY_STRING);
      add(gamePanel, GamePanel.DISPLAY_STRING);
   }

   public void showComponent(String displayString) {
      cardlayout.show(this, displayString);
   }

   private static void createAndShowGui() {
      GameState mainPanel = new GameState();

      JFrame frame = new JFrame("GameState");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

class StartPanel extends JPanel {
   public static final String DISPLAY_STRING = "Start Panel";

   public StartPanel(final GameState gameState, final GamePanel gamePanel) {
      add(new JButton(new AbstractAction("Start") {

         @Override
         public void actionPerformed(ActionEvent e) {
            gameState.showComponent(GamePanel.DISPLAY_STRING);
            gamePanel.startAnimation();
         }
      }));
   }
}

class GamePanel extends JPanel {
   public static final String DISPLAY_STRING = "Game Panel";
   private static final int PREF_W = 500;
   private static final int PREF_H = 400;
   private static final int RECT_WIDTH = 10;
   private int x;
   private int y;

   public void startAnimation() {
      x = 0;
      y = 0;
      int timerDelay = 10;
      new Timer(timerDelay , new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent e) {
            x++;
            y++;
            repaint();
         }
      }).start();
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      g.fillRect(x, y, RECT_WIDTH, RECT_WIDTH);
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }
}

答案 1 :(得分:1)

您应该使用SwingWorker这将在后台线程中执行doInBackground()中的代码,并在done()停止后执行EDT中doInBackground()中的代码

答案 2 :(得分:0)

最简单的方法:使用CountDownLatch。您将其设置为1,通过任何适当的方式使其在Swing代码中可用,并在主线程中await

答案 3 :(得分:0)

您可以考虑使用SwingUtilities.invokeAndWait()显示游戏面板的模态对话框,以便在关闭对话框时控件返回主线程。

答案 4 :(得分:0)

您可以使除EDT之外的所有代码在单线程执行服务上运行,然后只需在需要执行某些代码时发布runnables。