How to insert an event to the beginning of Event Dispatch Thread queue in java?
Asked Answered
M

3

12

I already know how Event Dispatch thread works. If there be short and long events in Event Dispatch thread like below, the application can't be responsive.

enter image description here

For the Sake of responsiveness in Swing, Event Dispatch thread should only be used for short events. while long events should be executed on SwingWorkers.

enter image description here

Imagine that there is a lot of short events.

enter image description here The events should be executed in Event Dispatch thread and you have a special event which you want to be executed before other events existing in Event Dispatch Thread queue. But, events will be enqueued to the end of the queue by default and even InvokeLater do the same.

So, is there any solution to enqueue an event to the beginning of the Event Dispatch Thread?

Mcphail answered 17/6, 2016 at 8:55 Comment(6)
Possible duplicate of this. Synchronize multiple SwingWorker instances like this.Narrative
If the events block the EDT then don't execute them on the EDT. You still have not provided a concrete example of what you are attempting to do. I have never used it but did you try invokeAndWait(...)?Aquamarine
Yes, I had done it. InvokeLater and InvokeAndWait is used for the new event respect to the thread it was called.Mcphail
EDT is designated to paint, repaint, refresh value, set value, apply changes, just for methods implemented in Swing API, all those events are dome in then same time, in one moment, all events thats contains queue in EDT, everything else is wrong idea, as aside invokeAndWait required false from EDT,Perkins
I never use it before, but I think solution to the problem is simple. You can simply put short tasks in a queue that feed tasks to the EDT. But I guess you have no control of it due to mechanism of Swing.Variegate
Bad idea. Part of the purpose of having the EventQueue is to order things for concurrency control. Inserting something at the beginning of the queue is asking for concurrency problems. If you can give a good reason to do this, I may rescind my judgement. The EDT should essentially always be available and ready to execute code because all the things going on to it are short tasks. Therefore, whatever you want to put at the front could just as well go at the end and execute essentially at the same time... but in a known good order. Be sure only the correct things are going on the EDT.Pauiie
L
5

Although replacing the EventQueue is a right approach, it's not really necessary since built-in EventQueue already supports prioritizing. Only thing is it only supports it for inner API use so we only need to understand how that works;

//from EventQueue.java...

private static final int LOW_PRIORITY = 0;
private static final int NORM_PRIORITY = 1;
private static final int HIGH_PRIORITY = 2;
private static final int ULTIMATE_PRIORITY = 3;

private static final int NUM_PRIORITIES = ULTIMATE_PRIORITY + 1;

/*
 * We maintain one Queue for each priority that the EventQueue supports.
 * That is, the EventQueue object is actually implemented as
 * NUM_PRIORITIES queues and all Events on a particular internal Queue
 * have identical priority. Events are pulled off the EventQueue starting
 * with the Queue of highest priority. We progress in decreasing order
 * across all Queues.
 */
private Queue[] queues = new Queue[NUM_PRIORITIES];

//...skipped some parts...

/**
 * Causes <code>runnable</code> to have its <code>run</code>
 * method called in the {@link #isDispatchThread dispatch thread} of
 * {@link Toolkit#getSystemEventQueue the system EventQueue}.
 * This will happen after all pending events are processed.
 *
 * @param runnable  the <code>Runnable</code> whose <code>run</code>
 *                  method should be executed
 *                  asynchronously in the
 *                  {@link #isDispatchThread event dispatch thread}
 *                  of {@link Toolkit#getSystemEventQueue the system EventQueue}
 * @see             #invokeAndWait
 * @see             Toolkit#getSystemEventQueue
 * @see             #isDispatchThread
 * @since           1.2
 */
public static void invokeLater(Runnable runnable) {
    Toolkit.getEventQueue().postEvent(
        new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
}

/**
 * Posts a 1.1-style event to the <code>EventQueue</code>.
 * If there is an existing event on the queue with the same ID
 * and event source, the source <code>Component</code>'s
 * <code>coalesceEvents</code> method will be called.
 *
 * @param theEvent an instance of <code>java.awt.AWTEvent</code>,
 *          or a subclass of it
 * @throws NullPointerException if <code>theEvent</code> is <code>null</code>
 */
public void postEvent(AWTEvent theEvent) {
    SunToolkit.flushPendingEvents(appContext);
    postEventPrivate(theEvent);
}

/**
 * Posts a 1.1-style event to the <code>EventQueue</code>.
 * If there is an existing event on the queue with the same ID
 * and event source, the source <code>Component</code>'s
 * <code>coalesceEvents</code> method will be called.
 *
 * @param theEvent an instance of <code>java.awt.AWTEvent</code>,
 *          or a subclass of it
 */
private final void postEventPrivate(AWTEvent theEvent) {
    theEvent.isPosted = true;
    pushPopLock.lock();
    try {
        if (nextQueue != null) {
            // Forward the event to the top of EventQueue stack
            nextQueue.postEventPrivate(theEvent);
            return;
        }
        if (dispatchThread == null) {
            if (theEvent.getSource() == AWTAutoShutdown.getInstance()) {
                return;
            } else {
                initDispatchThread();
            }
        }
        postEvent(theEvent, getPriority(theEvent));
    } finally {
        pushPopLock.unlock();
    }
}

private static int getPriority(AWTEvent theEvent) {
    if (theEvent instanceof PeerEvent) {
        PeerEvent peerEvent = (PeerEvent)theEvent;
        if ((peerEvent.getFlags() & PeerEvent.ULTIMATE_PRIORITY_EVENT) != 0) {
            return ULTIMATE_PRIORITY;
        }
        if ((peerEvent.getFlags() & PeerEvent.PRIORITY_EVENT) != 0) {
            return HIGH_PRIORITY;
        }
        if ((peerEvent.getFlags() & PeerEvent.LOW_PRIORITY_EVENT) != 0) {
            return LOW_PRIORITY;
        }
    }
    int id = theEvent.getID();
    if ((id >= PaintEvent.PAINT_FIRST) && (id <= PaintEvent.PAINT_LAST)) {
        return LOW_PRIORITY;
    }
    return NORM_PRIORITY;
}

/**
 * Posts the event to the internal Queue of specified priority,
 * coalescing as appropriate.
 *
 * @param theEvent an instance of <code>java.awt.AWTEvent</code>,
 *          or a subclass of it
 * @param priority  the desired priority of the event
 */
private void postEvent(AWTEvent theEvent, int priority) {
    if (coalesceEvent(theEvent, priority)) {
        return;
    }

    EventQueueItem newItem = new EventQueueItem(theEvent);

    cacheEQItem(newItem);

    boolean notifyID = (theEvent.getID() == this.waitForID);

    if (queues[priority].head == null) {
        boolean shouldNotify = noEvents();
        queues[priority].head = queues[priority].tail = newItem;

        if (shouldNotify) {
            if (theEvent.getSource() != AWTAutoShutdown.getInstance()) {
                AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
            }
            pushPopCond.signalAll();
        } else if (notifyID) {
            pushPopCond.signalAll();
        }
    } else {
        // The event was not coalesced or has non-Component source.
        // Insert it at the end of the appropriate Queue.
        queues[priority].tail.next = newItem;
        queues[priority].tail = newItem;
        if (notifyID) {
            pushPopCond.signalAll();
        }
    }
}

As you can see EventQueue have 4 different queues as LOW, NORM, HIGH and ULTIMATE, SwingUtilities.invokeLater(Runnable) or EventQueue.invokeLater(Runnable) wraps your Runnable into an InvocationEvent and calls postEvent(AWTEvent) method. This method does some syncronizing between threads and calls postEvent(AWTEvent, int) like this postEvent(theEvent, getPriority(theEvent)); Now the interesting part is how getPriority(AWTEvent) works, basicly it gives normal priority to the every event except some PaintEvents and PeerEvents.

So what you need to do is wrap your Runnable into a PeerEvent with ULTIMATE_PRIORTY instead of a InvocationEvent like this;

Toolkit.getDefaultToolkit().getSystemEventQueue()
   .postEvent(new PeerEvent(Toolkit.getDefaultToolkit(), () -> {


    //execute your high priority task here!
    System.out.println("I'm ultimate prioritized in EventQueue!");


}, PeerEvent.ULTIMATE_PRIORITY_EVENT));

You can check the full source code of EventQueue and PeerEvent .

Leopardi answered 19/6, 2016 at 23:47 Comment(1)
This is exactly what I was looking. Thanks @LeopardiMcphail
T
2

My initial thought was

I do not think we can control the tasks which needs to be picked up by Event Dispatch Thread, but in certain ways we can try to set the priority like below

SwingUtilities.invokeAndWait(new Runnable() {
  public void run() {
     Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
     // The task which need immediate attention.
}});

Again there is no guarantee that this would be picked up for immediate execution by EDT.

But the above code is wrong. By the time run gets called it is already executing the tasks. Thanks for the comments Onur.

So the code below should help.

   EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
   Runnable runnable = new Runnable() {

         @Override
         public void run() {
            //My high priority task
         }
   };
   PeerEvent event = new PeerEvent(this, runnable, PeerEvent.ULTIMATE_PRIORITY_EVENT);
   queue.postEvent(event);

But there is one point we need to notice.

    private static final int NUM_PRIORITIES = ULTIMATE_PRIORITY + 1;

    /*
     * We maintain one Queue for each priority that the EventQueue supports.
     * That is, the EventQueue object is actually implemented as
     * NUM_PRIORITIES queues and all Events on a particular internal Queue
     * have identical priority. Events are pulled off the EventQueue starting
     * with the Queue of highest priority. We progress in decreasing order
     * across all Queues.
     */
    private Queue[] queues = new Queue[NUM_PRIORITIES];

    public EventQueue() {
        for (int i = 0; i < NUM_PRIORITIES; i++) {
            queues[i] = new Queue();
        }
    ....
    }

So if we are setting too many ULTIMATE_PRIORITY tasks, there is no guarantee that the latest task would be executed immediately.

Tasimeter answered 19/6, 2016 at 17:30 Comment(3)
That code is not setting the Runnable's/Task's priory. It's setting Event Dispatch Thread's priority to the maximum. Which will not only effect current task but also effect every other task on the queue.Leopardi
@Leopardi I did notice just after your comments. Thank you for helping me learn.Tasimeter
you are welcome but the thing is Java's inner API never uses ULTIMATE_PRIORITY. You can see that here: Usages of sun.awt.PeerEvent PeerEvent always gets created with PeerEvent.PRIORITY_EVENTLeopardi
F
2

You can create and use your own Event Queue that inserts new events in the way you want it. See the code snippet below how to setup a custom Event Queue:

public class QueueTest {
    public static void main(String[] args) throws InterruptedException, InvocationTargetException {
        EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
        eventQueue.push(new MyEventQueue());

        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                System.out.println("Run");
            }
        });
    }

    private static class MyEventQueue extends EventQueue {
        public void postEvent(AWTEvent theEvent) {
            System.out.println("Event Posted");
            super.postEvent(theEvent);
        }
    }
}

Your custom Event Queue could then post specific events that you want to be prepended to the queue with the highest priority. This might not ensure that it is the next event to be processed, but would probably fit best into the existing design.

Ferrin answered 19/6, 2016 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.