3

I have a GUI app that is getting to be quite slow. I want to start introducing timing for various GUI tasks - however, many of our GUI actions trigger other actions which then "invoke later" to trigger other actions.

Eventually, it all settles down and there's nothing more to do. At this time, I want to stop a timer and report how long that GUI "action" took.

I figured the way to do this is to implement a method called invokeOnceIdle(Runnable task). The method will execute the supplied task only once the AWTEventQueue is "empty". i.e. the supplied "task" should be the last thing in the queue.

One way to do this would be is if there's a way to specify a "lowest" priority to SwingUtilities.invokeLater - but this isn't possible.

I next looked to see if I could "invokeLater" a Runnable which checks to see if the event queue is "empty" - but there's no public way to see if the event queue is actually empty.

What's the best way to do this?

Paul Hollingsworth
  • 11,954
  • 12
  • 48
  • 66

2 Answers2

5

Using your own event queue, you can easily reach that goal. Here something that I cooked up and should get you going:

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

All you have to do is push your "Once idle" runnables to the instance of the EventQueue using runOnceIdle()

Guillaume Polet
  • 45,783
  • 4
  • 79
  • 115
  • @PaulHollingsworth Basically, what your "is event queue empty" condition is translated in `EventQueue.peekEvent() == null` – Guillaume Polet Jun 26 '12 at 10:24
  • this doesn't guarantee that all events from model are done in the view, clear() & push() could be usefull after re_build GUI (model & view) after exceptions from RepaintManager, dot... – mKorbel Jun 26 '12 at 10:28
  • @GuillaumePolet: Is the class `MyEventQueue` inner class? – Crazenezz Jun 26 '12 at 10:34
  • @mKorbel No it does not guarantee that model-events are all done, this only guarantees that there are currently no more `AWTEvent`'s to dispatch which what the OP asked for. I am not sure of what you mean by "clear() & push() could be usefull after re_build GUI (model & view) after exceptions from RepaintManager, dot" – Guillaume Polet Jun 26 '12 at 10:34
  • @Crazenezz I used it as such in my code, but we could easily turn it into a Top-level class by changing its visibility. It does not matter if it is a top-level or an inner-class. – Guillaume Polet Jun 26 '12 at 10:35
  • hmmm ... fiddling with the EventQueue is a high-risk endeavour, so wouldn't if there's any other way. Probably don't fully understand the context, but why is it needed? SwingUtilities.invokeLater puts the runnable as last at the time of calling, so an option might be to let the runnable itself check whether the queue is empty at the time of being run - and then either run (if empty) or re-schedule as last. What am I missing? – kleopatra Jun 26 '12 at 10:41
  • @kleopatra Yes I also thought about that but I found that it was less elegant as you would have to repost SwingUtilities.invokeLater if there are still events on the queue. I would not really quite say that fidling with the event queue is high-risk but it can be considered as a "bold approach". I have done this quite a few times and never really had problems. – Guillaume Polet Jun 26 '12 at 10:47
  • 1
    @GuillaumePolet then you can see difference model v.s. view, result is Index/ArrayXxxExceptions, in the case context is rendered with RepaintManager exceptions too, – mKorbel Jun 26 '12 at 10:50
-1

isEventDispatchThread returns if is current AWTEventQueue is empty or not,

  • if is current AWTEventQueue is empty you can to post a new event(s) to EventQueue from

invokeAndWait() and invokeLater too

  • if is current AWTEventQueue is not empty then you can not to use invokeAndWait() only invokeLater()

for example

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;
    }
}
mKorbel
  • 108,320
  • 17
  • 126
  • 296
  • I thought isDispatchThread would simply tell me if the current thread is the Event Dispatch thread. But I already know that it is. All of the code is executing in the AWT thread - there are no other threads in our code. What I need to know is if there are other AWT event queue tasks pending still to be executed after the current task... – Paul Hollingsworth Jun 26 '12 at 09:50
  • upto Java7 is there only one EDT, you can't be able to multiply EDT and EDT has two states as you can see from my code example – mKorbel Jun 26 '12 at 09:52
  • I don't understand your example - even if there are multiple AWT event queues I still don't see how this will help me determine if any of them are "empty" – Paul Hollingsworth Jun 26 '12 at 09:59