如何在 java 中将事件插入到事件调度线程队列的开头?
How to insert an event to the beginning of Event Dispatch Thread queue in java?
我已经知道 Event Dispatch thread 的工作原理了。如果 Event Dispatch 线程中有短事件和长事件,如下所示,应用程序无法响应。
为了 Swing 的响应能力,事件调度线程应该只用于短事件。而长事件应该在 SwingWorkers 上执行。
假设有很多短事件。
事件应该在事件调度线程中执行,并且您有一个特殊事件要在事件调度线程队列中存在的其他事件之前执行。但是,默认情况下,事件将排到队列的末尾,甚至 InvokeLater
也是如此。
那么,是否有任何解决方案可以将事件排入事件调度线程的开头?
我最初的想法是
我认为我们无法控制事件调度线程需要接收的任务,但在某些方面我们可以尝试像下面这样设置优先级
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个任务,不能保证最新的任务会立即执行。
您可以创建和使用自己的事件队列,以您想要的方式插入新事件。请参阅下面的代码片段,了解如何设置自定义事件队列:
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);
}
}
}
然后,您的自定义事件队列可以 post 将您希望添加到具有最高优先级的队列中的特定事件。这可能无法确保它是下一个要处理的事件,但可能最适合现有设计。
虽然替换 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
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));
您可以查看 EventQueue and PeerEvent 的完整源代码。
我已经知道 Event Dispatch thread 的工作原理了。如果 Event Dispatch 线程中有短事件和长事件,如下所示,应用程序无法响应。
为了 Swing 的响应能力,事件调度线程应该只用于短事件。而长事件应该在 SwingWorkers 上执行。
假设有很多短事件。
InvokeLater
也是如此。
那么,是否有任何解决方案可以将事件排入事件调度线程的开头?
我最初的想法是
我认为我们无法控制事件调度线程需要接收的任务,但在某些方面我们可以尝试像下面这样设置优先级
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个任务,不能保证最新的任务会立即执行。
您可以创建和使用自己的事件队列,以您想要的方式插入新事件。请参阅下面的代码片段,了解如何设置自定义事件队列:
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);
}
}
}
然后,您的自定义事件队列可以 post 将您希望添加到具有最高优先级的队列中的特定事件。这可能无法确保它是下一个要处理的事件,但可能最适合现有设计。
虽然替换 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
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));
您可以查看 EventQueue and PeerEvent 的完整源代码。