5

I use SwingUtilities.invokeLater() frequently. Doing so, however, makes it difficult to debug in certain cases: you can't see a stack trace of the code that called SwingUtilities.invokeLater(), because that code has already ended its execution.

Are there any suggestions for how to set some sort of context (for debugging purposes only) when calling SwingUtilities.invokeLater(), so that you can figure out what caused the UI event in question?

Jason S
  • 184,598
  • 164
  • 608
  • 970

6 Answers6

2

Most of the other answers here are good, but I want to add another suggestion. If you are calling SwingUtilities.invokeLater very frequently, you are probably doing it unnecessarily some of the time, especially if the sole purpose of the call is to ensure that Swing changes are made on the event thread. Try this when appropriate:

if (SwingUtilities.isEventDispatchThread()) {
  myRunnable.run();
} else {
  SwingUtilities.invokeLater(myRunnable);
}
DJClayworth
  • 26,349
  • 9
  • 53
  • 79
  • So SwingUtilities doesn't already do this as part of its invokeLater() implementation? – Jason S Jan 04 '11 at 17:36
  • No, because invokeLater only executes 'later', after all pending events have been processed. Obviously if you need that behaviour you can't use this trick. invokeAndWait may also work as an alternative. – DJClayworth Jan 05 '11 at 17:51
2

You can try to override EventQueue and print stacktrace for posted events. Also in the example below a unique number would be assigned to each posted event. When invokeLater would be called from other invokeLater then the text postEvent 9 from 7 would be printed in the log

        // Place this code somewhere in the main class to override queue
        EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
        eventQueue.push(new MyEventQueue());

Where class MyEventQueue might look like this:

import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.awt.event.InvocationEvent;
import java.util.WeakHashMap;

public class MyEventQueue extends EventQueue {

    int currentNumber = 0;
    WeakHashMap<AWTEvent,Integer> eventIdMap = new WeakHashMap<AWTEvent,Integer>();
    AWTEvent currentEvent = null;

    protected void dispatchEvent(AWTEvent event) {
        if (event instanceof InvocationEvent) {
            currentEvent = event;
        }
        super.dispatchEvent(event);
        currentEvent = null;
    }

    public void postEvent(AWTEvent event) {
        if (event instanceof InvocationEvent) {
            currentNumber = currentNumber + 1;
            eventIdMap.put(event, currentNumber);
            System.out.println("postEvent " + currentNumber + " " +
                    (currentEvent != null ? "from " + eventIdMap.get(currentEvent) : "") );
            for(StackTraceElement element : new RuntimeException().getStackTrace()) {
                System.out.println("\t" + element);
            }
        }
        super.postEvent(event);
    }
}
sign
  • 389
  • 1
  • 3
  • 8
  • As a clarification, one `extends EventQueue` and overrides the desired method, e.g. `invokeLater()`. Here's a related example, http://stackoverflow.com/questions/3158409 – trashgod Jan 05 '11 at 03:31
1

Override the method, add a Log call and then call the real one... Caveat : you have to replace all your calls to the original method.

You can even wrap the runnable and add a context number (for example the timestamp of the call to invoke later). When the runnable starts it began to print the context number

 public static void myInvokeLater(final Runnable runnable) {
   long ts = System.currentTimeMillis();
   Log.info("call to invoke later, context :" + ts);
   Runnable r = new Runnable() {
            public void run() {
                Log.info("start runnable of invokeLater with context :" + ts);
                runnable.run();
            }
        };
   SwingUtilities.invokeLater(r);
}
Twister
  • 749
  • 4
  • 9
  • If you System.out.println(runnable) and System.out.println(this) within the run method of the runnable, you should be able to match them up :) – Chris Dennett Jan 04 '11 at 16:47
  • And not easily deactivable. there you can just add if (DEBUG) then do the magic, else just forward to the real InvokeLater. – Twister Jan 04 '11 at 17:37
1

I would tend to replace "standard" swingUtilites#invokeLater method by a more advanced one, maybe embedded in some "localUtilities", to which you give both the code to execute with, as an argument, the source event or a thread-safe copy of it (I guess you have a source event, whatever its type is).

Riduidel
  • 22,052
  • 14
  • 85
  • 185
1

If you are calling invokeLater frequently, you might want to consider simplifying your threading.

invokeLater is effectively using mutable statics and is therefore purest evil. Testing and more will become easier if you switch from calling EventQueue.invokeLater to using an interface with invokeLater and isDispatchThread.

It is unfortunate that you can't, in general, replace the invokeLater and isDispatchThread used by libraries.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • Could you explain your comment about "mutable statics"? I don't see any reason why you need to pass mutable data across the UI/non-UI boundary. – Anon Jan 04 '11 at 16:53
  • @Anon The implementation of static `invokeLater` has to be picking a [mutable] static event queue somewhere along the line. – Tom Hawtin - tackline Jan 04 '11 at 17:00
  • So you're suggesting replacing the static calls to `SwingUtilities` with an (injectable) instance? – Anon Jan 04 '11 at 17:02
  • @Anon Yes. (Although I object to "injectable" - I prefer "Parameterisation from Above" or "using constructors correctly".) – Tom Hawtin - tackline Jan 04 '11 at 17:06
  • Ditto for your side comment! I avoided reading information about dependency injection for months because I thought it was some evil hacker technique for overriding good code with arbitrary bad code. – Jason S Jan 04 '11 at 17:35
1

Are you passing anonymous Runnables to invokeLater()?

If yes, I'd suggest replacing them with non-anonymous classes, which will add a couple of lines of code, but will give you at least some level of traceability (eg: TableUpdateFromQuery). This works best if you invoke a particular type of update from only one place in the app. It also leads you down the path of "background activities," which can be tested outside of the UI.

Anon
  • 2,654
  • 16
  • 10