如何将事件插入到java中的事件调度线程队列的开头?

时间:2016-06-17 08:55:42

标签: java swing event-dispatch-thread

我已经知道Event Dispatch thread是如何运作的。如果在下面的事件调度线程中有短事件和长事件,则应用程序无法响应。

enter image description here

对于Swing中的响应清醒,Event Dispatch线程仅应用于短事件。而应该在SwingWorkers上执行长事件。

enter image description here

想象一下,有很多短暂的事件。

enter image description here 事件应该在Event Dispatch线程中执行,并且您有一个特殊事件,您希望在Event Dispatch Thread队列中存在的其他事件之前执行该事件。但是,默认情况下,事件将排队到队列的末尾,甚至InvokeLater也会这样做。

那么,是否有任何解决方案将事件排入事件调度线程的开头?

3 个答案:

答案 0 :(得分:4)

虽然替换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 ULTIMATESwingUtilities.invokeLater(Runnable)EventQueue.invokeLater(Runnable)Runnable包裹到InvocationEvent并调用postEvent(AWTEvent) } 方法。这个方法在线程和调用postEvent(AWTEvent, int)之间进行了一些同步,就像这样postEvent(theEvent, getPriority(theEvent));现在有趣的部分是getPriority(AWTEvent)如何工作,基本上它给每个事件提供了正常的优先级,除了一些PaintEvent s和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));

您可以查看EventQueuePeerEvent的完整源代码。

答案 1 :(得分:2)

我最初的想法是

我认为我们无法控制Event Dispatch Thread需要接收的任务,但在某些方面我们可以尝试设置优先级,如下所示

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

同样不能保证EDT会立即执行此操作。

但上面的代码是错误的。到运行时被调用它已经在执行任务。感谢Onur的评论。

所以下面的代码应该有所帮助。

   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 任务,则无法保证最新任务会立即执行。

答案 2 :(得分:2)

您可以创建和使用自己的事件队列,以您希望的方式插入新事件。请参阅下面的代码段,了解如何设置自定义事件队列:

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

然后,您的自定义事件队列可以发布您希望预先添加到具有最高优先级的队列的特定事件。这可能无法确保它是下一个要处理的事件,但可能最适合现有设计。