EDT队列切割

时间:2013-08-05 15:37:17

标签: java multithreading swing event-dispatch-thread

有这篇文章:


有人跳了队! 偶尔看来,一些挥杆事件在事件队列中以不正确的顺序处理(并且当有人切入队列时没有任何东西让我的血液沸腾)导致奇怪的行为。最好用小代码片段来说明这一点。阅读下面的代码段,仔细考虑您想象的事件发生的顺序。

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent arg0) {
         repaint();
         doSomething();
    }
});

大多数开发人员会认为repaint()方法将导致在doSomething()方法调用之前进行绘制操作。但事实并非如此,对repaint()的调用将创建一个新的绘制事件,该事件将添加到事件队列的末尾。只有当前的Action Event完成后才会处理(调度)此新的paint事件。这意味着将在调度队列上的新Paint事件之前执行doSomething()方法。

这里的关键点是调用repaint()将创建一个新的绘制事件,该事件将被添加到结束事件队列中而不会立即处理。这意味着没有事件跳过队列(我的血液可以保持在正确的温度)。

(source)


我的问题是,如何强制Swing在repaint();之前doSomething();进行操作?

此外,如果在repaint()内调用doSomething();方法,则只有在doSomething();完成后才会执行这些方法。有没有办法我可以暂停doSomething(); mid-executin,然后投入reapaint();,完成它,然后恢复doSomething();

到目前为止我找到的解决方案只有this(link),但它并不实用......

2 个答案:

答案 0 :(得分:6)

嗯,你和引文的作者在这里忽略了这一点。 “重绘”方法调用只是通知重绘管理器:

  1. 有一个要重新绘制的组件(在其上称为“重绘”)
  2. 应该用(x,y,w,h)剪辑重新绘制(如果你调用“重绘”没有指定矩形-it将是整个组件边界,(0,0,w,h))< / LI>

    因此,当> 重复出现时,重要的事情并不重要,因为如果您为同一个组件逐个调用大量重新绘制,您可能甚至都没有注意到它。这也是为什么这样的后续调用可能合并的原因。检查此示例:

    private static int repaintCount = 0;
    
    public static void main ( String[] args )
    {
        final JComponent component = new JComponent ()
        {
    
            protected void paintComponent ( Graphics g )
            {
                try
                {
                    // Simulate heavy painting method (10 milliseconds is more than enough)
                    Thread.sleep ( 10 );
                }
                catch ( InterruptedException e )
                {
                    e.printStackTrace ();
                }
    
                g.setColor ( Color.BLACK );
                g.drawLine ( 0, 0, getWidth (), getHeight () );
    
                repaintCount++;
                System.out.println ( repaintCount );
            }
        };
        component.setPreferredSize ( new Dimension ( 200, 200 ) );
    
        JFrame frame = new JFrame ();
        frame.add ( component );
        frame.pack ();
        frame.setLocationRelativeTo ( null );
        frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
        frame.setVisible ( true );
    
        new Thread ( new Runnable ()
        {
            public void run ()
            {
                try
                {
                    Thread.sleep ( 1000 );
                }
                catch ( InterruptedException e )
                {
                    e.printStackTrace ();
                }
                System.out.println ( "Starting repaint calls" );
                for ( int i = 0; i < 100000; i++ )
                {
                    component.repaint ();
                }
                System.out.println ( "Finishing repaint calls" );
            }
        } ).start ();
    }
    

    这是您将看到的近似输出(可能因计算机速度,Java版本和许多其他条件而异):

    1
    Starting repaint calls
    2
    3
    4
    5
    6
    Finishing repaint calls
    7
    8
    

    “1” - 显示帧时的初始重绘 “2,3,4 ......” - 由于来自另一个非EDT线程的电话,发生了其他七次重绘。

    “但我已经打了100000个重拍,而不是7个!” - 你会说。是的,重新绘制管理器合并了那些在重新绘制队列中相似但同时的内容。这是为了优化重新绘制并加快UI的整体效果。

    顺便说一下,你不需要从EDT调用重绘,因为它不会执行任何真正的绘画,只是为了将来排队你的组件更新。它已经是线程安全的方法。

    要总结一下 - 在执行其他操作之前,您确实需要重新绘制组件(这也可能导致其再次重新绘制)。只需在重新绘制组件时调用重绘(尽可能使用指定的矩形) - 重绘管理器将完成剩下的工作。除非你在paint方法中加入一些完全错误并且可能会导致很多问题的计算,否则这种方法很有效。

答案 1 :(得分:2)

repaint()将新的绘制请求添加到事件调度线程(EDT)的队列末尾。因此,在repaint()内对doSomething()进行多次调用只会在doSomething()完成后重新绘制。 (我假设始终从EDT调用doSomething()。您的代码示例从actionPerformed内部调用doSomething(),它始终从EDT调用。)

paint()请求排队的原因是减少组件绘制的次数。对repaint()请求进行排队允许使用多种方法将不同的组件标记为脏,以便在一次昂贵的paint()操作中同时重新绘制它们。

如果您真的想立即强制重绘,有paintImmediately(Rectangle r)paintImmediately(int x, int y,int w,int h)等方法,但您必须知道要重新绘制的尺寸。

如果您对Swing组件正在使用的当前Graphics有一个引用,您也可以自己调用paint(Graphics g)。 (如果要截取屏幕截图并将其写入图片文件,也可以使用此技术从Image对象创建自己的图形对象。