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

前端 未结 3 2081
忘掉有多难
忘掉有多难 2021-02-20 00:45

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.

<
相关标签:
3条回答
  • 2021-02-20 00:55

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

    0 讨论(0)
  • 2021-02-20 01:13

    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.

    0 讨论(0)
  • 2021-02-20 01:13

    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.

    0 讨论(0)
提交回复
热议问题