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

前端 未结 3 1577
不知归路
不知归路 2021-02-20 00:20

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:56

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

提交回复
热议问题