通过频繁更新了解真实模型中的EDT

时间:2014-05-21 14:44:12

标签: java swing concurrency event-dispatch-thread

我正在用Java编写Sugarscape模拟,需要一个可用的GUI。 Sugarscape是一个空间景观,由瓷砖(糖),药剂移动和消费糖组成。为简单起见,我只有一个代理人而且没有糖 - 我只是想让代理人感动。

在过去的两周里,我已经阅读了painting in javaconcurrency in java,并发摆动,我阅读了肮脏的富客户端和无数的StackOverflow线程,但我必须在这里提出问题。

我需要将我的模型与GUI分开。这提出了一个问题,因为99%的教程建议在其他方法中调用重绘。我的想法是运行一个" tick"模拟:所有代理移动,然后发送一个Event(我的GUI类扩展Observer),然后触发一个repaint();请求并更新GUI。然而问题(误解)在于SwingUtilities.InvokeLater方法。我的代码是:

public void setupGUI()
{
    SwingUtilities.invokeLater(new Runnable() 
    {
        public void run() {
            System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
            SugarFrame frame = new SugarFrame(simulation.getWorld());
            frame.setVisible(true);
        }

    });

}

为了理解发生了什么,我到处都插入了println。事件的顺序令我感到困惑:


控制台输出:

1.Agent创建。起始位置:X = 19 Y = 46 //这是在代理构造函数

2.模拟开始。实验编号:0

  1. 现在正在EDT上设置GUI吗? true //如上所示,这是在SwingUtilities.InvokeLater部分中。但随后EDT暂停,真正的模型继续:

  2. 勾号0

  3. 调用代理操作,触发TickStart事件

  4. TickStartEvent已创建

  5. 调用代理操作,从现在开始for循环

  6. 现在正在移动代理商0:

  7. 现在正在消费糖。

  8. 现在搬家。

  9. 现在睡觉。

  10. 已创建Sugarframe并添加了Grid。全部在EDT?是的//它又回来了。随后是paint组件,并显示代理可见的窗口。

  11. 在EDT上调用paintComponent?真


  12. 现在,我已经读过,通过让主线程处于休眠状态,你可以给EDT时间来运行重绘。但是,这只发生过一次。永远不再调用重绘,我只看到模型的一次迭代。

    我根本不明白我错过了哪些信息可以正确使用EDT。定期建议使用Swingworker和Swingtimer,但是对于每个建议都有一个概念,即我的模型不需要它们。 paintComponent根本没有被调用,或排队到最后(然后仍然没有重新绘制,即使我使用thread.sleep)。

    我很感激任何帮助。为长篇大论道歉。

    //编辑:根据请求提供更多代码。 整个主要方法:

    public class SimulationController {
    
    static Simulation simulation;
    
    public static final int NUM_EXPERIMENTS = 1;
    
    public SimulationController() 
    {
        Random prng = new Random();
        SimulationController.simulation = new Simulation(prng);
    }
    
    public void run() {
    
        setupGUI();
        for(int i=0; i<NUM_EXPERIMENTS; i++) {
            System.out.println("Simulation start. Experiment number: " + i);
            simulation.getWorld().addObserver(simulation);
            simulation.addObserver(simulation.getWorld());
            simulation.run();
        }
    }
    
    public void setupGUI()
    {
        SwingUtilities.invokeLater(new Runnable() 
        {
            public void run() {
                System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
                SugarFrame frame = new SugarFrame(simulation.getWorld());
                frame.setVisible(true);
            }
    
        });
    
    }
    
    
    public static void main(String[] args) {
        SimulationController controller = new SimulationController();
        controller.run();
    
    }
    

    }

    我的JPanel类中的绘制覆盖:

        @Override
    public void paintComponent(Graphics g) {
        System.out.println(">>>>>>>>paintComponent called, on EDT? " + SwingUtilities.isEventDispatchThread()+"<<<<<<<<<<");
        super.paintComponent(g);
        //g.clearRect(0, 0, getWidth(), getHeight());
        rectWidth = getWidth() / world.getSizeX();
        rectHeight = getHeight() / world.getSizeY();
    
        for (int i = 0; i < world.getSizeX(); i++) 
        {
            for (int j = 0; j < world.getSizeY(); j++) 
            {
                // Upper left corner of this terrain rect
                x = i * rectWidth;
                y = j * rectHeight;
                Tile tile = world.getTile(new Position(i, j));
                if (tile.hasAgent()) 
                {
                    g.setColor(Color.red);
                } else 
                    {
                    g.setColor(Color.black);
                    }
    
                g.fillRect(x, y, rectWidth, rectHeight);
            }
    
        }
       } 
    

    再次使用JPanel类,更新方法:

        public void update(Observable o, Object arg) 
    {   
        if (arg instanceof TickEnd)
        {
            TickEvent tickEndevent = new TickEvent();
            this.addTickEvent(tickEndevent);
        }
    
        }
    } 
    
       private final BlockingQueue<TickEvent> TICK_EVENTS = new LinkedBlockingQueue<TickEvent>();
    
    /**Runnable object that updates the GUI (I think)**/
    private final Runnable processEventsRunnable = new Runnable()
    {
        public void run()
        {
            TickEvent event = new TickEvent();
            while ((event = TICK_EVENTS.poll()) != null)
                    {
                    System.out.println("This is within processEventsRunnable, inside the While loop. Repaint is called now.");
                    repaint();
                    }
    
        }
    };
    
    
    /**Add Event to the processing-Events-queue**/
    public void addTickEvent(TickEvent event)
    {
        //System.out.println("This is in the Add TickEvent method, but before the adding.  "+TICK_EVENTS.toString());
    
        TICK_EVENTS.add(event);
        System.out.println("TickEvent has been added!  "+TICK_EVENTS.toString() + "On EDT?" + SwingUtilities.isEventDispatchThread());
    
    
        if (TICK_EVENTS.size() >= 1)
        {
            SwingUtilities.invokeLater(processEventsRunnable);
        }
    }
    

    最后但并非最不重要的是,JFrame构造函数:

    /** Sugarframe Constructor**/
    public SugarFrame(World world)
    {
        super("Sugarscape");    // creates frame, the constructor uses a string argument for the frame title
        grid = new Grid(world); // variable is declared in the class
        add(grid);
        setDefaultCloseOperation(EXIT_ON_CLOSE);  // specifies what happens when user closes the frame. exit_on_close means the program will stop
        this.setContentPane(grid);
        this.getContentPane().setPreferredSize(new Dimension(500, 500));
        this.pack(); // resizes frame to its content sizes (rather than fixed height/width)
        System.out.println("The Sugarframe has been created and Grid added. All on EDT? "+ SwingUtilities.isEventDispatchThread());
    
        this.setVisible(true); // makes the Frame appear on screen
    }
    

1 个答案:

答案 0 :(得分:1)

句子,

  

我需要将我的模型与GUI分开。这提出了一个问题,因为99%的教程建议在其他方法中调用重绘。

  

现在,我已经读过,通过让主线程处于休眠状态,你可以给EDT时间来运行重绘。

对我来说听起来不对,所以我会尝试清理一些事情,也许如果你重新评估这些陈述背后的基本想法,你可以找到你所掌握的信息。丢失。

首先,请始终牢记我们所讨论的这种调度模型。你不能说&#34; EDT现在为我做这个!&#34;。它总是&#34; EDT这是你需要做的另外一项任务,当你完成了你正在做的任何事情时做到这一点&#34;。所以EDT有一个&#34;任务队列#34;要做,并逐一消耗它。

这些任务通常由事件创建:按下按钮为EDT提供了一项任务,当GUI组件的状态发生变化时,某些听众可能会被通知并在EDT中排队一些工作。但是,你也可以直接说&#34; EDT执行这段代码,稍后&#34;。这就是你对invokeLater所做的事情,你可以安排在EDT任何时候做的工作。即使您从EDT呼叫invokeLater,任务也会被安排,而不会在此时执行。

同样的情况发生在invokeAndWait是的,代码按顺序执行,就好像它现在被执行一样,但它仍然是一个预定的工作。所以repaint()也不例外。 repaint()不会重新绘制GUI,而是安排重新绘制GUI。

然而,repaint()在可以从outside the EDT调用的意义上是例外的!这并不奇怪,因为我们知道唯一能做的就是安排某项工作,它实际上并没有弄乱GUI,所以你可以随意调用它。

这意味着该行

SwingUtilities.invokeLater(processEventsRunnable);

其中processEventsRunnable基本上执行repaint()是没有意义的,整个滴答系统过于复杂和不必要。您只需在GUI上更改某些内容或GUI上提供的数据时调用repaint(),以便更改反映在屏幕上。

此外,如果你想做一些需要在EDT中执行的事情(比如用分数更改Label的文本),你可以将代码放在主线程的invokeLater块中。这将正确排队并执行任务,您不需要自己创建事件队列系统。

记住以下所有这一点毫无意义:

  

我已经看过,通过让主线程进入休眠状态,你可以给EDT时间来运行重绘

在调用repaint()后不久,GUI将自动更新。主要做很多事情并调用大量重绘不会阻止GUI更新。但是,如果你想睡觉&#34;主要因此变化的速度很慢,所以用户可以在屏幕上欣赏它,你应该使用计时器。

因此,只要您的主要访问GUI值和方法,只要您定期更改数据,就可以随时调用重绘。

编辑:你也有一个主线程做事听起来有点奇怪。正如您在并发章节中所读到的那样,通常您只是在EDT中创建GUI,然后当按下按钮等时,应用程序主要是事件驱动的。如果需要定期更改,请使用计时器。您可以使用辅助线程来执行特定的非GUI相关繁重工作,例如读取文件。但是,作为设计的一部分,您通常不会永久激活辅助线程。

以下是一个非常简单的程序,可以定期移动方块。我只是使用计时器来更改数据并调用repaint()。请注意,我使用SwingTimer(它在EDT中执行),因为我想检查面板宽度。否则我可以在任何线程中运行计时器的代码。

在你的情况下,你可能有你的&#34;地图&#34;独立于GUI存储,因此您只需要检查该数据,以便在需要时(键盘按下,定期...)正确移动代理的坐标。

看起来像这样:

example

完整代码:

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

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class MovingSquareTest
{
    int x, y, size, step;
    MyPanel panel;
    Timer timer;

    public static final void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                MovingSquareTest app = new MovingSquareTest();
                app.createAndShowGUI();
                app.timer.start();
            }
        });
    }

    public MovingSquareTest()
    {
        x = 0;
        y = 150;
        size = 50;
        step = 50;

        timer = new Timer(500, new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                x += step;
                if (x < 0) x = 0;
                if (x + size > panel.getWidth()) x = panel.getWidth() - size;
                if (x == 0 || x + size == panel.getWidth()) step *= -1;
                panel.repaint();
            }
        });
    }

    public void createAndShowGUI()
    {
        JFrame frame =  new JFrame("Dance, my square!");

        panel = new MyPanel();

        frame.add(panel);

        frame.setSize(600, 400);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

    }

    private class MyPanel extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);

            g.drawRect(x, y, size, size);
        }
    }
}