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

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

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

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

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

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

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

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


person Hamed    schedule 17.06.2016    source источник
comment
Возможный дубликат этого. Синхронизируйте несколько экземпляров SwingWorker, например это.   -  person trashgod    schedule 17.06.2016
comment
Если события блокируют EDT, не выполняйте их в EDT. Вы до сих пор не привели конкретного примера того, что вы пытаетесь сделать. Я никогда не использовал его, но вы пробовали invokeAndWait(...)?   -  person camickr    schedule 17.06.2016
comment
Да, я сделал это. InvokeLater и InvokeAndWait используются для нового события, относящегося к потоку, в котором оно было вызвано.   -  person Hamed    schedule 17.06.2016
comment
EDT предназначен для рисования, перерисовки, обновления значения, установки значения, применения изменений только для методов, реализованных в Swing API, все эти события происходят в одно и то же время, в один момент, все события, которые содержат очередь в EDT, все остальное неправильная идея, так как в стороне invokeAndWait требуется false от EDT,   -  person mKorbel    schedule 17.06.2016
comment
Я никогда не использовал его раньше, но я думаю, что решение проблемы простое. Вы можете просто поставить короткие задачи в очередь, которая подает задачи в EDT. Но я думаю, вы не можете это контролировать из-за механизма Swing.   -  person Junbang Huang    schedule 19.06.2016


Ответы (3)


Хотя замена 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.

Итак, что вам нужно сделать, это обернуть 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 .

person Onur    schedule 19.06.2016
comment
Это именно то, что я искал. Спасибо @Онур - person Hamed; 22.06.2016

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

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

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

person T. Neidhart    schedule 19.06.2016

Первоначально я подумал

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

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

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

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

Так что приведенный ниже код должен помочь.

   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, нет никакой гарантии, что последняя задача будет выполнена немедленно.

person Beniton    schedule 19.06.2016
comment
Этот код не устанавливает приоритет Runnable/Task. Он устанавливает максимальный приоритет потока отправки событий. Это повлияет не только на текущую задачу, но и на все остальные задачи в очереди. - person Onur; 20.06.2016
comment
@Onur Я заметил сразу после твоих комментариев. Спасибо, что помогли мне учиться. - person Beniton; 20.06.2016
comment
пожалуйста, но дело в том, что внутренний API Java никогда не использует ULTIMATE_PRIORITY. Вы можете увидеть это здесь: Использование sun.awt.PeerEvent PeerEvent всегда создается с PeerEvent.PRIORITY_EVENT - person Onur; 20.06.2016