如何在Java Swing中实现空闲任务

4
我有一个GUI应用程序,变得相当缓慢。我想开始为各种GUI任务引入计时 - 然而,我们的许多GUI操作会触发其他操作,然后“稍后调用”以触发其他操作。
最终,一切都平息下来,没有更多事情要做了。这时,我想停止计时器并报告该GUI“操作”所需的时间。
我认为做到这一点的方法是实现一个名为invokeOnceIdle(Runnable task)的方法。该方法将仅在AWTEventQueue为空时执行提供的任务。即,提供的“任务”应该是队列中的最后一件事。
一种方法是如果有一种方法可以指定SwingUtilities.invokeLater的“最低”优先级 - 但这是不可能的。
接下来,我试图“invokeLater”一个Runnable,该Runnable检查事件队列是否为空 - 但是没有公共方法可以看到事件队列是否实际为空。
最好的方法是什么?
2个回答

5
使用自己的事件队列,您可以轻松实现这个目标。下面是我准备的示例代码,应该可以帮助您入门:
private static class MyEventQueue extends EventQueue {

    private Deque<Runnable> onceIdle = new LinkedList<Runnable>();

    public MyEventQueue() {
        Toolkit.getDefaultToolkit().getSystemEventQueue().push(this);
    }

    public void runOnceIdle(Runnable toRun) {
        onceIdle.addLast(toRun);
    }

    @Override
    protected void dispatchEvent(AWTEvent event) {
        super.dispatchEvent(event);
        if (peekEvent() == null) {
            for (Runnable toRun : onceIdle) {
                toRun.run();
            }
            onceIdle.clear();
        }
    }
}

您只需使用runOnceIdle()将"一旦空闲"的任务推送到事件队列的实例即可。


这并不保证模型中的所有事件都在视图中完成,clear()和push()可以在RepaintManager出现异常后重新构建GUI(模型和视图)时很有用。 - mKorbel
@mKorbel 不,这并不能保证模型事件都已完成,它只能保证当前没有更多的AWTEvent需要分发,这是OP所要求的。我不确定你所说的“在RepaintManager出现异常后重新构建GUI(模型和视图)时,clear()和push()可能会有用”。 - Guillaume Polet
@Crazenezz 我在我的代码中就是这样使用的,但我们可以通过改变其可见性轻松地将其转换为顶级类。它是顶级类还是内部类并不重要。 - Guillaume Polet
嗯……玩弄EventQueue是一项高风险的尝试,所以最好找其他方法。也许我没有完全理解上下文,但为什么需要它呢?SwingUtilities.invokeLater在调用时将可运行对象放在最后,因此一个选项可能是让可运行对象自己检查队列是否为空并在运行时运行(如果为空)或将其重新安排到最后。我错过了什么吗? - kleopatra
1
@GuillaumePolet 然后您可以看到模型与视图之间的差异,结果是Index/ArrayXxxExceptions,在这种情况下,上下文与RepaintManager异常一起呈现。 - mKorbel
显示剩余3条评论

-1

isEventDispatchThread 返回当前 AWTEventQueue 是否为空。

  • 如果当前的 AWTEventQueue 为空,则可以通过 invokeAndWait()invokeLater() 将新事件发布到 EventQueue。

invokeAndWait()invokeLater()

  • 如果当前的 AWTEventQueue 不为空,则只能使用 invokeLater(),不能使用 invokeAndWait()

例如:

import java.awt.EventQueue;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;

public class IsThereEDT {

    private ScheduledExecutorService scheduler;
    private AccurateScheduledRunnable periodic;
    private ScheduledFuture<?> periodicMonitor;
    private int taskPeriod = 30;
    private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    private Date dateRun;
    private JFrame frame1 = new JFrame("Frame 1");

    public IsThereEDT() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        periodic = new AccurateScheduledRunnable() {

            private final int ALLOWED_TARDINESS = 200;
            private int countRun = 0;
            private int countCalled = 0;
            private int maxCalled = 10;

            @Override
            public void run() {
                countCalled++;
                if (countCalled < maxCalled) {
                    if (countCalled % 3 == 0) {
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                System.out.println("Push a new event to EDT");
                                frame1.repaint();
                                isThereReallyEDT();
                            }
                        });
                    } else {
                        if (this.getExecutionTime() < ALLOWED_TARDINESS) {
                            countRun++;
                            isThereReallyEDT(); // non on EDT
                        }
                    }
                } else {
                    System.out.println("Terminating this madness");
                    System.exit(0);
                }
            }
        };
        periodicMonitor = scheduler.scheduleAtFixedRate(periodic, 0, taskPeriod, TimeUnit.SECONDS);
        periodic.setThreadMonitor(periodicMonitor);
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                isThereReallyEDT();
                frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame1.getContentPane().add(new JLabel("Hello in frame 1"));
                frame1.pack();
                frame1.setLocation(100, 100);
                frame1.setVisible(true);
            }
        });
        try {
            Thread.sleep(500);
        } catch (InterruptedException ex) {
            Logger.getLogger(IsThereEDT.class.getName()).log(Level.SEVERE, null, ex);
        }
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame2 = new JFrame("Frame 2");
                frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame2.getContentPane().add(new JLabel("Hello in frame 2"));
                frame2.pack();
                frame2.setLocation(200, 200);
                frame2.setVisible(true);
                isThereReallyEDT();
            }
        });
    }

    private void isThereReallyEDT() {
        dateRun = new java.util.Date();
        System.out.println("                         Time at : " + sdf.format(dateRun));
        if (EventQueue.isDispatchThread()) {
            System.out.println("EventQueue.isDispatchThread");
        } else {
            System.out.println("There isn't Live EventQueue.isDispatchThread, why any reason for that ");
        }
        if (SwingUtilities.isEventDispatchThread()) {
            System.out.println("SwingUtilities.isEventDispatchThread");
        } else {
            System.out.println("There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        IsThereEDT isdt = new IsThereEDT();
    }
}

abstract class AccurateScheduledRunnable implements Runnable {

    private ScheduledFuture<?> thisThreadsMonitor;

    public void setThreadMonitor(ScheduledFuture<?> monitor) {
        this.thisThreadsMonitor = monitor;
    }

    protected long getExecutionTime() {
        long delay = -1 * thisThreadsMonitor.getDelay(TimeUnit.MILLISECONDS);
        return delay;
    }
}

我原本认为isDispatchThread只是告诉我当前线程是否为事件分发线程。但事实上,我已经知道它是的。我们所有的代码都在AWT线程中执行 - 代码中没有其他线程。我需要知道的是,在当前任务之后是否仍有其他AWT事件队列任务等待执行... - Paul Hollingsworth
在Java7之前只有一个EDT,你不能够创建多个EDT,并且EDT有两种状态,正如你从我的代码示例中所看到的。 - mKorbel
我不理解你的例子 - 即使有多个AWT事件队列,我仍然不明白这如何帮助我确定它们中的任何一个是否“空闲”。 - Paul Hollingsworth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接