javafx
Многопоточность
Поиск…
Обновление пользовательского интерфейса с помощью Platform.runLater
Длительные операции не должны запускаться в потоке приложений JavaFX, так как это предотвращает обновление JavaFX пользовательского интерфейса, что приводит к замороженному интерфейсу.
Кроме того, любое изменение в Node
которое является частью «живого» графика сцены, должно происходить в потоке приложений JavaFX. Platform.runLater
можно использовать для выполнения этих обновлений в потоке приложений JavaFX.
В следующем примере показано, как обновлять Text
Node
несколько раз из другого потока:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class CounterApp extends Application {
private int count = 0;
private final Text text = new Text(Integer.toString(count));
private void incrementCount() {
count++;
text.setText(Integer.toString(count));
}
@Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.getChildren().add(text);
Scene scene = new Scene(root, 200, 200);
// longrunning operation runs on different thread
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Runnable updater = new Runnable() {
@Override
public void run() {
incrementCount();
}
};
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
// UI update is run on the Application thread
Platform.runLater(updater);
}
}
});
// don't let thread prevent JVM shutdown
thread.setDaemon(true);
thread.start();
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Группировка обновлений пользовательского интерфейса
Следующий код делает пользовательский интерфейс неактивным в течение короткого времени после нажатия кнопки, так как используется слишком много вызовов Platform.runLater
. (Попробуйте прокрутить ListView
сразу после нажатия кнопки.)
@Override
public void start(Stage primaryStage) {
ObservableList<Integer> data = FXCollections.observableArrayList();
ListView<Integer> listView = new ListView<>(data);
Button btn = new Button("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
new Thread(() -> {
for (int i = 0; i < 100000; i++) {
final int index = i;
Platform.runLater(() -> data.add(index));
}
}).start();
});
Scene scene = new Scene(new VBox(listView, btn));
primaryStage.setScene(scene);
primaryStage.show();
}
Чтобы предотвратить это, вместо использования большого количества обновлений, следующий код использует AnimationTimer
для запуска обновления только один раз для каждого кадра:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
public class Updater {
@FunctionalInterface
public static interface UpdateTask {
public void update() throws Exception;
}
private final List<UpdateTask> updates = new ArrayList<>();
private final AnimationTimer timer = new AnimationTimer() {
@Override
public void handle(long now) {
synchronized (updates) {
for (UpdateTask r : updates) {
try {
r.update();
} catch (Exception ex) {
Logger.getLogger(Updater.class.getName()).log(Level.SEVERE, null, ex);
}
}
updates.clear();
stop();
}
}
};
public void addTask(UpdateTask... tasks) {
synchronized (updates) {
updates.addAll(Arrays.asList(tasks));
timer.start();
}
}
}
который позволяет группировать обновления с использованием класса Updater
:
private final Updater updater = new Updater();
...
// Platform.runLater(() -> data.add(index));
updater.addTask(() -> data.add(index));
Как использовать службу JavaFX
Вместо того, чтобы запускать интенсивные задачи в JavaFX Thread
который должен быть сделан в Service
что же такое служба ?
Служба - это класс, который создает новый Thread
каждый раз, когда вы его запускаете, и передаёт ему задачу, чтобы выполнить некоторую работу. Служба может вернуть или не значение.
Ниже приведен типичный пример службы JavaFX, которая выполняет некоторую работу и возвращает
Map<String,String>(
):
public class WorkerService extends Service<Map<String, String>> {
/**
* Constructor
*/
public WorkerService () {
// if succeeded
setOnSucceeded(s -> {
//code if Service succeeds
});
// if failed
setOnFailed(fail -> {
//code it Service fails
});
//if cancelled
setOnCancelled(cancelled->{
//code if Service get's cancelled
});
}
/**
* This method starts the Service
*/
public void startTheService(){
if(!isRunning()){
//...
reset();
start();
}
}
@Override
protected Task<Map<String, String>> createTask() {
return new Task<Map<String, String>>() {
@Override
protected Void call() throws Exception {
//create a Map<String, String>
Map<String,String> map = new HashMap<>();
//create other variables here
try{
//some code here
//.....do your manipulation here
updateProgress(++currentProgress, totalProgress);
}
} catch (Exception ex) {
return null; //something bad happened so you have to do something instead of returning null
}
return map;
}
};
}
}