【问题标题】:Understanding the EDT in a real model with frequent updates了解频繁更新的真实模型中的 EDT
【发布时间】:2014-05-21 14:44:12
【问题描述】:

我正在用 Java 编写 Sugarscape 模拟,需要一个可用的 GUI。 Sugarscape 是一种空间景观,由(糖的)瓷砖和移动和消耗糖的代理组成。为简单起见,我只有一个代理,没有糖——我只想看到代理移动。

在过去的两周里,我读过painting in javaconcurrency in java、swing 并发,我读过肮脏的富客户端和无数的 StackOverflow 线程,但我必须在这里提出一个问题。

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

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

  1. 现在在 EDT 上设置 GUI? true // 如您在上面看到的,这是在 SwingUtilities.InvokeLater 部分内。但随后 EDT 暂停,真实模型继续:

  2. 刻度数 0

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

  4. 已创建 TickStartEvent

  5. 调用代理操作,for-loop 现在开始

  6. 0 号特工现在搬家:

  7. 现在吃糖。

  8. 现在移动。

  9. 现在睡觉。

  10. Sugarframe 已创建并添加了网格。全部在美国东部时间? true // 然后它又回来了。绘制组件随后出现,代理可见的窗口出现。

  11. 在 EDT 上调用了paintComponent?真的


现在,我已经了解到,通过让主线程进入睡眠状态,您可以给 EDT 时间来运行重绘。然而,这只会发生一次。 Repaint 再也不会被调用,而且我只看到过模型的一次迭代。

我只是不明白我缺少哪些信息才能正确使用 EDT。 Swingworker 和 Swingtimer 经常被推荐,但对于每一个建议,都有一个概念,即像我这样的模型不需要它们。要么根本不调用paintComponent,要么排队到最后(即使我使用thread.sleep,仍然不会重新绘制)。

如果有任何帮助,我将不胜感激。为长篇道歉。

//编辑:根据要求添加更多代码。 整个main方法:

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
}

【问题讨论】:

  • 模拟的其余部分实际发生在哪里?最好提供更多关于模拟其余部分的代码,因为仅从跟踪中准确了解正在发生的事情是很复杂的。
  • 嗯,我有大约 17 节课。一个类,模拟,需要所有对象并包括调用模型动作的方法。 SimulationController 是建立模型的主要方法。 SugarFrame 是 JFrame,Grid 是 JPanel。我尝试添加一段相关的代码,因为我无法在此处发布所有代码。
  • “现在,我已经读到,通过让主线程进入睡眠状态,你可以给 EDT 时间来运行重绘。”这是没有意义的。 EDT 处理代码块,主要是事件。 EDT “有时间”来处理您安排的事件,只要它不忙于较旧的事件。显然,处理每个事件非常快,您唯一需要担心的是您没有在其中一个事件中阻塞 EDT。例如,在 EDT 中调用 sleep 是一个主要问题,因为这会停止所有事件处理并且 GUI 冻结。主线程的状态无关紧要。
  • 您能否详细说明“我需要将我的模型与 GUI 分开”。如果程序背后的逻辑和数据很好并且值得推荐,那么“模型”是什么意思。教程通常在其他方法中调用 repaint() 只是因为它们在修改 GUI 后调用它,无论在哪里,都没有等待的意义。另请注意,repaint() 实际上并不重绘 GUI,而是在 EDT 中安排重绘任务。只要它是免费的,它就会被执行。这将很快,但它与调用是异步的。
  • 是的,“模型”是指糖景的逻辑和数据。 IE。我为 GUI 提供了单独的类,这些类将依赖于从主模型传递的事件,而不是使用糖或移动包含与 GUI 相关的代码的方法。是的,正是日程安排让我头疼。我不知道如何适应异步线程。

标签: java swing concurrency event-dispatch-thread


【解决方案1】:

句子,

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

现在,我已经了解到,通过让主线程进入睡眠状态,您可以给 EDT 时间来运行重绘。

对我来说听起来不太对,所以我会尝试澄清一下,也许如果你重新评估这些陈述背后的基本思想,你会发现你遗漏的信息。

首先,请始终牢记我们所讨论的这种调度模型。你不能说“EDT 现在为我做这件事!”。它始终是“EDT 这里是你需要做的另一项任务,当你完成你正在做的任何事情时去做”。所以 EDT 有一个“任务”队列要做,并一个一个地处理它。

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

invokeAndWait 也是如此,是的,代码是按顺序执行的,就好像它在此刻执行一样,但它仍然是一个预定的工作。所以repaint() 也不例外。 repaint() 不会重绘 GUI,而是安排重绘 GUI。

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

这意味着该行

SwingUtilities.invokeLater(processEventsRunnable);

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

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

记住所有这些是没有意义的:

我已经读到,通过让主线程进入睡眠状态,您可以给 EDT 时间来运行重绘

在您调用 repaint() 后不久,GUI 将自行更新。 main 做了很多事情并调用了很多 repaints 并不能阻止 GUI 被更新。但是,如果您想“休眠”主要内容,以便更改的速度很慢,以便用户可以在屏幕上欣赏它,您应该使用计时器。

因此,只要您的 main 不访问 GUI 值和方法,只要您定期或不定期更改数据,请随时调用 repaint。

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

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

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

看起来像这样:

完整代码:

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);
        }
    }
}

【讨论】:

  • 感谢您的回答和对睡眠句子的澄清,也许我措辞不好。但是当按下按钮时我不需要发生任何事情,也不需要通过计时器进行更改。当模型中的代理移动时,我需要进行更改。就像你在它“移动”的每一步都重新绘制俄罗斯方块一样。在您的情况下, actionperformed 会监听计时器。但我需要它来聆听广场的移动。所以:当计时器击中时不要重新绘制,而是:正方形已经移动,重新绘制。明天我会再看一下我的主要课程,非常感谢!
  • 在这种情况下,您可能会发现 PropertyChangeListenerobserver-observable pattern 很有趣。我仍然想知道模型中的代理在什么标准/情况下发生变化。
  • 我正在使用 Observer-Observable。但是在 update() 中调用 repaint 似乎好得令人难以置信(因为它确实不起作用)。代理改变它的位置,就这么简单。它所在的图块发生了变化。
  • @ursath 我不明白你的评论/问题。现在在做什么?应该怎么做?是什么导致数据更改并触发更新?瓦片是 GUI 对象还是只是一个位置?
  • 瓦片是一个包含位置对象的类,它也是一个类。一个位置就是 x 和 y,两个整数。它没有图形用户界面。当我在我的 Grid(GUI) 类的 update 方法中编写 repaint 时,它侦听滴答事件和 agentmoved-events,绘制请求没有被执行。甚至在模拟结束时也没有。
猜你喜欢
  • 2010-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
  • 1970-01-01
  • 2020-08-02
  • 2011-07-15
相关资源
最近更新 更多