EDT队列切割

Kar*_*120 3 java swing multithreading event-dispatch-thread

有这篇文章:


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

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent arg0) {
         repaint();
         doSomething();
    }
});
Run Code Online (Sandbox Code Playgroud)

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

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

(资源)


我的问题是,我怎么能强迫的Swing做repaint();之前doSomething();

此外,如果有方法调用repaint()WITHIN,doSomething();它们将仅在doSomething();完成后执行.有没有办法可以暂停doSomething();中间执行,然后投入reapaint();,完成它,然后恢复doSomething();

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

Mik*_*rin 6

那么,你和引用文章的作者在这里忽略了这一点."重绘"方法调用只是通知重绘管理器:

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

因此,重新绘制的时间并不重要,因为如果您为同一个组件逐个调用大量重绘,您可能甚至都不会注意到它.这也是为什么这样的后续调用可能合并的原因.检查此示例:

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 ();
}
Run Code Online (Sandbox Code Playgroud)

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

1
Starting repaint calls
2
3
4
5
6
Finishing repaint calls
7
8
Run Code Online (Sandbox Code Playgroud)

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

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

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

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