SwingUtilities.invokeLater берет Runnable и запускает его на EDT?
Меня смущает подпись SwingUtilities.invokeLater
, Требуется Runnable
объект. Это объект Runnable, который передается в поток диспетчеризации событий? Почему я не могу позвонить напрямую createAndShowGUI
на run
метод EDT
(если это возможно)?
Я читал статьи на SO о том, как работают EDT и invokeLater, но меня смущает Runnable
объект, который передается.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
И что будет, если я позвоню SwingUtilities.invokeLater
снова прямо под вызовом?
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
doSomethingOnTopOfGUI();
}
});
2 ответа
Что такое новый Runnable() {}?
new Runnable() {
public void run() {
createAndShowGUI();
}
};
Это объявляет анонимный класс и создает его новый экземпляр. Это в основном эквивалентно этому:
class Creator implements Runnable {
public void run() {
createAndShowGUI();
}
}
new Creator();
До лямбды Java 8 анонимный класс - это способ выполнять функциональное программирование. Передача Runnable в invokeLater
это как передача функции, которую EDT может выполнить позже, когда захочет (путем вызова run
в теме).
Что invokeLater делает с Runnable?
Изначально все, что он делает, - это создает событие, чтобы обернуть его ( InvocationEvent) и поместить его в конец очереди.
После постановки в очередь, invokeLater
уведомляет EDT (который просыпается, если ожидал). Собственный метод выполнения EDT представляет собой бесконечный цикл, который обрабатывает события. Это выглядит примерно так (очень перефразировано):
public void run() {
while(true) {
EventQueue eq = getEventQueue();
synchronized(eq) {
while(eq.hasNextEvent()) {
processOneEvent(eq.getNextEvent());
}
try {
eq.wait();
} catch(InterruptedException ie) {}
}
}
}
Когда EDT добирается до нового InvocationEvent, он вызывает run
на Runnable, который вызывает createAndShowGUI
,
Таким образом, передавая Runnable для invokeLater
или же invokeAndWait
позволяет запускать любой код, который вы хотите на EDT.
Как скоро пробежка называется?
В большинстве случаев сразу или почти сразу. EDT очень отзывчивый. "Поздняя" часть "invokeLater" нам подсказывает, что:
run
выполняется асинхронно. Поток, который вызываетinvokeLater
может продолжаться после вызова до обработки события.- Другие события могут быть обработаны в первую очередь.
Конечно invokeAndWait
существует в качестве альтернативы, если синхронный вызов run
желательно (Хотя следует отметить, что invokeAndWait
может также вызвать обработку других событий, если они были поставлены в очередь перед новым InvocationEvent.)
Асинхронный
SwingUtilities.invokeLater(new Runnable() {
public void run() {
System.out.println("hello");
}
});
System.out.println("world");
Это может распечатать
Привет Мир
и это может напечатать
Мир Привет
потому что вызывающий поток может продолжаться или не продолжаться до (или даже во время) обработки события. Это зависит от системного планировщика потоков.
синхронный
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
System.out.println("hello");
}
});
System.out.println("world");
Это обязательно напечатает
Привет Мир
потому что вызывающий поток будет ждать run
завершить до продолжения (если не выдается исключение).
invokeAndWait
использует схему ожидания и уведомления для достижения этой цели. Объект монитора создается и передается в InvocationEvent. invokeAndWait
звонки wait
после публикации события и вызовов InvocationEvent notifyAll
после run
завершает на EDT. Схема ожидания и уведомления является причиной invokeAndWait
не может быть вызвано на EDT. У EDT нет возможности остановиться в середине одного события для обработки другого.
Что произойдет, если в очереди будет находиться более одного события?
EDT обрабатывает события по одному, в порядке их очередей и в соответствии с их приоритетом. Некоторые события также могут быть объединены (обычное InvocationEvent не будет). Большинство событий имеют нормальный приоритет. PaintEvent (как от звонка repaint
) обычно имеет низкий приоритет, а некоторые системные события имеют более высокий приоритет.
В конкретном примере вы дали:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
doSomethingElse();
}
});
Поскольку они относятся к одному виду событий и имеют одинаковый приоритет, doSomethingElse
событие будет обработано после createAndShowGUI
событие сделано.
Точно так же, если что-то подобное было сделано:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
System.out.println("world");
}
});
System.out.println("hello");
}
});
Это будет печатать
Привет Мир
поскольку world
Runnable запускается после hello
Runnable завершается.
Ты можешь использовать SwingUtilities.invokeLater()
из любого потока, чтобы поставить задачу в очередь на EDT. Это полезно в некоторых ситуациях, особенно в многопоточных приложениях, в качестве удобного способа обновления элементов графического интерфейса из потоков, не относящихся к графическому интерфейсу, а также при запуске приложения (на что намекает ваш пример) из main()
, который запускается в основном потоке, а не в EDT.
Например:
// main is not executed in the EDT
public static void main (String[] args) {
// create and initialize GUI in the EDT to avoid potential thread-safety issues.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
Возможно, вы захотите взглянуть на официальный учебник по теме "Рассылка событий", который должен прояснить некоторые ваши неправильные представления о том, что такое EDT и как вы к нему обращаетесь.