Jason S
Jason S

Reputation: 189646

Java: debugging with SwingUtilities.invokeLater()

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?

Upvotes: 5

Views: 1323

Answers (6)

DJClayworth
DJClayworth

Reputation: 26856

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

Upvotes: 2

sign
sign

Reputation: 407

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

Upvotes: 2

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147154

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.

Upvotes: 1

Anon
Anon

Reputation: 2674

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.

Upvotes: 1

Twister
Twister

Reputation: 724

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

Upvotes: 1

Riduidel
Riduidel

Reputation: 22292

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).

Upvotes: 1

Related Questions