Java 在频繁更新的真实模型中理解EDT

Java 在频繁更新的真实模型中理解EDT,java,swing,concurrency,event-dispatch-thread,Java,Swing,Concurrency,Event Dispatch Thread,我正在用Java编写Sugarscape模拟,需要一个工作的GUI。Sugarscape是一个空间景观,由(糖)瓷砖和移动和消费糖的代理组成。简单来说,我只有一个代理,没有糖-我只想看到代理移动 在过去的两周里,我阅读了《swing中的并发》(concurrency in swing),阅读了肮脏的富客户端和无数的StackOverflow线程,但我必须在这里提出一个问题 我需要将我的模型与GUI分开。这是一个问题,因为99%的教程建议在其他方法中调用重新绘制。我的想法是运行模拟的一个“滴答声”

我正在用Java编写Sugarscape模拟,需要一个工作的GUI。Sugarscape是一个空间景观,由(糖)瓷砖和移动和消费糖的代理组成。简单来说,我只有一个代理,没有糖-我只想看到代理移动

在过去的两周里,我阅读了《swing中的并发》(concurrency in swing),阅读了肮脏的富客户端和无数的StackOverflow线程,但我必须在这里提出一个问题

我需要将我的模型与GUI分开。这是一个问题,因为99%的教程建议在其他方法中调用重新绘制。我的想法是运行模拟的一个“滴答声”:所有代理移动,然后发送一个事件(我的GUI类扩展了Observer),然后触发重新绘制();请求并更新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.已创建代理。起始位置:X=19 Y=46//这是在代理构造函数中

2.模拟启动。实验编号:0

  • 正在EDT上设置GUI?true//如上所述,这在SwingUtilities.InvokeLater部分中。但随后EDT暂停,真实模型继续:

  • 勾选数字0

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

  • TickStartEvent已创建

  • 调用代理操作,以便立即开始循环

  • 0号代理正在移动:

  • 现在正在吃糖

  • 现在就走

  • 现在睡觉

  • Sugarframe已经创建并添加了网格。都在EDT吗?没错,它又回来了。随后将显示绘制组件,并显示代理可见的窗口

  • 在EDT上调用了一个组件?真的



  • 现在,我已经读到,通过将主线程置于睡眠状态,您可以给EDT运行重新绘制的时间。然而,这种情况只发生一次。再也不会调用Repaint,我只看到模型的一次迭代

    我只是不明白我在正确使用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();
    
    }
    
    这些句子

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


    现在,我已经读到,通过将主线程置于睡眠状态,您可以给EDT运行重新绘制的时间

    我觉得这听起来不太对,所以我会把事情弄清楚一点,也许如果你重新评估你在这些陈述背后的基本想法,你会发现你遗漏的信息

    首先,请始终记住我们所讨论的调度模型。你不能说“现在就帮我做这个!”。它总是“EDT这里还有一个任务你需要做,当你完成你正在做的事情时就去做”。因此,EDT有一个“任务”队列要做,并且一个接一个地执行

    这些任务通常由事件创建:按下按钮给EDT一个任务,当GUI组件的状态发生变化时,一些监听器可能会收到通知,并将一些工作排入EDT队列。但是,您也可以直接说“EDT稍后执行这段代码”。这是您使用
    invokeLater
    所做的,您可以在EDT中安排一项工作,只要它是空闲的。即使您从EDT调用
    invokeLater
    ,任务也会被安排,而不是在此时执行

    invokeAndWait
    yes也会发生同样的情况,代码会按顺序执行,就好像它是在此时执行的一样,但它仍然是一个计划的工作。因此,
    repaint()
    也不例外
    repaint()
    不重新绘制GUI,而是计划重新绘制GUI

    然而,
    repaint()
    是一个例外,因为它可以从中调用!这并不奇怪,因为我们知道,唯一做的事情就是安排某项工作,它实际上不会干扰GUI,因此您可以在任何地方调用它

    这意味着

    SwingUtilities.invokeLater(processEventsRunnable);
    
    其中
    processEventsRunnable
    基本上执行
    repaint()
    是没有意义的,整个勾号系统过于复杂且不必要。当您在GUI或GUI提供的数据上更改某些内容时,只需调用repaint(),这样更改就会反映在屏幕上

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

    将所有这些牢记在心以下是毫无意义的:

    我已经读到,通过使主线程休眠,您可以给EDT运行重新绘制的时间

    调用repaint()后不久,GUI将自行更新。main做了很多事情并调用了很多重绘,但这并不能阻止GUI的更新。然而,如果你想“睡眠”的主要,所以改变的速度是缓慢的,用户可以欣赏它在屏幕上,你应该使用定时器

    因此,只要main不访问GUI值和方法,无论是否定期更改数据,都可以随时调用repaint

    编辑:还有我
        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);
        }
    }
    
    /** 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
    }
    
    SwingUtilities.invokeLater(processEventsRunnable);
    
    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);
            }
        }
    }