Статьи

JavaFx: скрытое правило потоков


Следующий пост является результатом моих последних экспериментов JavaFX Script ,
смешанных с
Stu серии «s Java.next ( 1 , 2 ). Итак, Стю решил решить, как языки Java.next взаимодействуют с языком Java, и для этого он выбрал пример на основе Swing. Несмотря на то, что он проделал большую работу, охватывая каждое преобразование из исходного примера Java в каждый из кандидатов Java.next, оригинальный пример имеет небольшой недостаток: он не соответствует рекомендациям Sun по созданию приложений Swing, главным образом при создании компонентов и / или изменяя состояние пользовательского интерфейса компонента, вы должны сделать это в потоке диспетчеризации событий.

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

  • плохие примеры программирования с первых дней Swing.
  • нет способа обеспечить соблюдение рекомендаций Sun Swing на уровне компилятора (некоторые инструменты могут обеспечивать более строгие проверки)

Потому что количество плохих примеров (под плохими я имею в виду те, которые не следуют рекомендациям) намного больше, чем хороших, которые мы продолжаем падать в одну и ту же дыру снова и снова. Вторая причина может быть смягчена внешними инструментами, такими как средства проверки кода и IDE. Следующий пример следует рекомендациям Sun, что подтверждает высокий уровень церемоний языка Java

import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class Swing {
public static void main(String[] args) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JFrame frame = new JFrame("Hello Swing");
JButton button = new JButton("Click Me");

button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
JOptionPane.showMessageDialog(null,
String.format("<html>Hello from <b>Java</b><br/>" +
"Button %s pressed", event.getActionCommand()));
}
});

frame.getContentPane().add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
} );
}
}

В Java Swing вы всегда должны принимать крайние меры предосторожности в отношении многопоточности, один плохой ход, и ваше приложение будет не отвечать на запросы при выполнении долгосрочных вычислений, пугая пользователя. Обычные приложения Swing будут запускаться из основного потока, что оставляет вам задачу правильно обернуть ваш код с помощью SwingUtilities. Какое это имеет отношение к JavaFx Script? там происходит полная противоположность. Каждое отдельное приложение JavaFx всегда выполняется в потоке диспетчеризации событий, точка. Это означает, что вам больше не нужно беспокоиться о проблемах рисования / перекраски при изменении состояния пользовательского интерфейса, но вам все равно нужно позаботиться о длинных вычислениях, так как они будут выполняться внутри EDT. Следующий скрипт fx демонстрирует этот факт

import java.lang.System;
import javax.swing.SwingUtilities;
import java.lang.Thread;

class MyThread extends Thread {
override function run():Void {
System.out.println("inside MyThread");
System.out.println( SwingUtilities.isEventDispatchThread() );
}
}

System.out.println( SwingUtilities.isEventDispatchThread() );
var t = new MyThread();
t.start();
// --- output
// true
// inside MyThread
// false

 Как только вы выпрыгните из EDT, вам в конечном итоге придется вернуться, чтобы обновить состояние пользовательского интерфейса. На данный момент у JavaFx нет альтернативы ярлыков (команда jfx все еще работает над этим), поэтому, возможно, нам пока не стоит отказываться от SwingWorker. В случае, если вам интересно, вы не можете расширить SwingWorker из JavaFx Script (по крайней мере, не так, как я мог бы найти), учитывая, что он не поддерживает обобщенные объявления (пока). Альтернатива прямо сейчас довольно проста: перейдите на уровень Java, создайте подкласс SwingWorker и используйте его из своих сценариев JavaFx.

Теперь я хотел бы показать альтернативу Groovy с измененным примером, вместо отображения диалога при нажатии кнопки, его текст изменит цвет с черного на красный и обратно

import groovy.swing.SwingBuilder
import javax.swing.SwingUtilities
import static javax.swing.JFrame.EXIT_ON_CLOSE
import java.awt.Color

int count = 0
SwingBuilder.build {
println "building ui inside EDT? ${SwingUtilities.isEventDispatchThread()}"
frame( title: "Hello Swing", pack: true, show: true,
defaultCloseOperation: EXIT_ON_CLOSE ) {
button( "Click Me", actionPerformed: { evt ->
println "action handler inside EDT? ${SwingUtilities.isEventDispatchThread()}"
doOutside {
println "long computation inside EDT? ${SwingUtilities.isEventDispatchThread()}"
doLater {
println "UI state change inside EDT? ${SwingUtilities.isEventDispatchThread()}"
evt.source.foreground = count++ % 2 ? Color.BLACK : Color.RED
}
}
})
}
}

Запустив пример, вы получите следующий вывод

создание интерфейса внутри EDT? правда

Это означает, что SwingBuilder позаботился о правильном построении пользовательского интерфейса внутри EDT, статический build()метод работает на EDT. Нажав на кнопку один раз, вы получите следующий вывод

обработчик действия внутри EDT? правда

длинные вычисления внутри EDT?
ложное


изменение состояния интерфейса внутри EDT?
правда

Два метода должны привлечь ваше внимание: doOutsideи doLater. Это удобные потоковые абстракции, предоставляемые SwingBuilder. Первый порождает новую нить и запускает закрытие, второй проверяет, что закрытие выполняется внутри EDT. Просто, легко, Groovy. Но если этих средств многопоточности недостаточно, вы всегда можете создать подкласс SwingWorker (в Groovy, так как он поддерживает дженерики и другие функции Java5) и подключить его.

Дорога к JavaFx Script 1.0 все еще впереди, платформа JavaFx все еще находится в стадии разработки. , ожидайте некоторых обновлений по потокам, надеюсь, к моменту выпуска 1.0.

</ rant>

Примечание:
я не обладаю какой-либо внутренней информацией о JavaFx, весь этот материал является результатом моих собственных исследований и публикаций в блогах по всему Интернету.