java - EDT队列切割

标签 java multithreading swing event-dispatch-thread

有这篇文章:


有人插队了! 时不时地,一些 Swing 事件似乎在事件队列中以错误的顺序处理(没有什么比有人插队更让我热血沸腾的了)导致奇怪的行为。这最好用一个小代码片段来说明。阅读下面的片段并仔细思考您想象的事件发生的顺序。

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

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

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

(source)


我的问题是,如何强制 Swing 执行 repaint();之前 doSomething();

此外,如果有调用 repaint() doSomething(); 中的方法它们只会在 doSomething(); 之后执行完成了。有什么办法可以暂停 doSomething();中间执行,然后抛出 reapaint(); ,完成它,然后继续 doSomething();

目前我找到的唯一解决方案是 this(link) , 但它不是很实用...

最佳答案

好吧,您和所引用文章的作者都没有捕获要点。 “重绘”方法调用只是通知重绘管理器:

  1. 有一个要重绘的组件(你称之为“重绘”)
  2. 它应该用 (x,y,w,h) 剪辑重新绘制(如果您调用“重新绘制”而没有指定 rect - 它将是整个组件边界,(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 ();
}

这是您将看到的近似输出(可能因计算机速度、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 方法中进行一些计算,否则这种方法效果很好,但这是完全错误的,可能会导致很多问题。

关于java - EDT队列切割,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18062126/

相关文章:

java - 将 JTextField 输入限制为整数

java - JPanel不会出现在JTabbedPane中?

linux - 如何加快此 curl 脚本并更快地响应

java - 单击按钮打开静态表单/jFrame

java - 正则表达式添加换行符 ->总是添加一个太多

java - 需要在此表上添加表头

java - 如何同步两个队列到线程构造函数的传递?

c - pthread 信号不起作用

java - 非常基本的 JFrame 未显示

Java Swing.Timer 获取实时毫秒数