Как вставить событие в начало очереди событий Dispatch в java?

Я уже знаю, как работает поток событий Dispatch. Если в потоке Event Dispatch есть короткие и длинные события, как показано ниже, приложение не может реагировать.

введите описание изображения здесь

В ответ на отзывчивость в Swing поток Event Dispatch должен использоваться только для коротких событий. в то время как длинные события должны выполняться на SwingWorkers.

введите описание изображения здесь

Представьте, что существует множество коротких событий.

введите описание изображения здесь События должны быть выполнены в потоке "Диспетчер событий", и у вас есть специальное событие, которое вы хотите выполнить перед другими событиями, существующими в очереди событий Dispatch. Но события будут помещены в очередь до конца очереди по умолчанию, и даже InvokeLater сделает то же самое.

Итак, есть ли какое-либо решение вставить событие в начало потока Dispatch Event?

Ответ 1

Хотя замена EventQueue является правильным подходом, это не обязательно, поскольку встроенный EventQueue уже поддерживает приоритизацию. Единственное, что он поддерживает только для внутреннего использования API, поэтому нам нужно только понять, как это работает:

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

Как вы видите, EventQueue имеет 4 разных очереди: LOW, NORM, HIGH and ULTIMATE, SwingUtilities.invokeLater(Runnable) или EventQueue.invokeLater(Runnable) обертывает ваш Runnable в InvocationEvent и вызывает метод postEvent(AWTEvent). Этот метод выполняет некоторую синхронизацию между потоками и вызовами postEvent(AWTEvent, int), как это. postEvent(theEvent, getPriority(theEvent)); Теперь интересной частью является то, как работает getPriority(AWTEvent), в основном это дает нормальный приоритет каждому событию, кроме некоторых PaintEvent и PeerEvent s.

Итак, что вам нужно сделать, это обернуть Runnable в PeerEvent с помощью ULTIMATE_PRIORTY вместо InvocationEvent следующим образом:

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

Вы можете проверить полный исходный код EventQueue и PeerEvent.

Ответ 2

Моя первоначальная мысль была

Я не думаю, что мы можем управлять задачами, которые нужно подобрать в Event Dispatch Thread, но в некоторых случаях мы можем попытаться установить приоритет, как показано ниже

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

Опять же нет никакой гарантии, что это будет подобрано для немедленного выполнения EDT.

Но приведенный выше код неверен. К моменту запуска вызова он уже выполняет задачи. Спасибо за комментарии Онур.

Таким образом, код ниже должен помочь.

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

Но есть один момент, который мы должны заметить.

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

Поэтому, если мы устанавливаем слишком много задач ULTIMATE_PRIORITY, нет гарантии, что последняя задача будет выполнена немедленно.

Ответ 3

Вы можете создать и использовать свою собственную очередь событий, которая вставляет новые события так, как вы этого хотите. См. Фрагмент кода ниже, как настроить пользовательскую очередь событий:

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

Пользовательская очередь событий может затем отправлять определенные события, которые вы хотите добавить в очередь с наивысшим приоритетом. Это может не гарантировать, что это следующее событие, которое будет обработано, но, вероятно, лучше всего подходит к существующему дизайну.