Периодическая фоновая задача JavaFX
Я пытаюсь запустить фоновый поток приложения JavaFX периодически, что изменяет некоторые свойства GUI.
Я думаю, что я знаю, как использовать классы Task
и Service
из javafx.concurrent
и не могу понять, как запустить такую периодическую задачу без использования метода Thread#sleep()
. Было бы неплохо, если бы я мог использовать некоторые методы Executor
from Executors
(Executors.newSingleThreadScheduledExecutor()
)
Я пытался запускать Runnable
каждые 5 секунд, который перезапускает javafx.concurrent.Service
, но он сразу же висит, когда вызывается service.restart
или даже service.getState()
.
Итак, я использую Executors.newSingleThreadScheduledExecutor()
, который запускает мой Runnable
каждые 5 секунд и что Runnable
запускает другой Runnable
, используя:
Platform.runLater(new Runnable() {
//here i can modify GUI properties
}
Это выглядит очень неприятно:( Есть ли лучший способ сделать это, используя классы Task
или Service
?
Ответы
Ответ 1
Вы можете использовать Timeline в этом отношении:
Timeline fiveSecondsWonder = new Timeline(new KeyFrame(Duration.seconds(5), new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("this is called every 5 seconds on UI thread");
}
}));
fiveSecondsWonder.setCycleCount(Timeline.INDEFINITE);
fiveSecondsWonder.play();
для фоновых процессов (которые ничего не делают с пользовательским интерфейсом) вы можете использовать старый добрый java.util.Timer
:
new Timer().schedule(
new TimerTask() {
@Override
public void run() {
System.out.println("ping");
}
}, 0, 5000);
Ответ 2
Я бы предпочел использовать PauseTransition:
PauseTransition wait = new PauseTransition(Duration.seconds(5));
wait.setOnFinished((e) -> {
/*YOUR METHOD*/
wait.playFromStart();
});
wait.play();
Ответ 3
Вот решение, использующее Java 8 и ReactFX. Скажите, что вы хотите периодически пересчитывать значение Label.textProperty()
.
Label label = ...;
EventStreams.ticks(Duration.ofSeconds(5)) // emits periodic ticks
.supplyCompletionStage(() -> getStatusAsync()) // starts a background task on each tick
.await() // emits task results, when ready
.subscribe(label::setText); // performs label.setText() for each result
CompletionStage<String> getStatusAsync() {
return CompletableFuture.supplyAsync(() -> getStatusFromNetwork());
}
String getStatusFromNetwork() {
// ...
}
По сравнению с решением Сергея вы не посвящаете весь поток получать статус из сети, а вместо этого используете для этого общий пул потоков.
Ответ 4
Вы также можете использовать ScheduledService
. Я использую эту альтернативу, заметив, что во время использования Timeline
и PauseTransition
в моем приложении происходило зависание пользовательского интерфейса, особенно когда пользователь взаимодействует с элементами MenuBar
(в JavaFX 12). При использовании ScheduledService
эти проблемы больше не возникали.
class UpdateLabel extends ScheduledService<Void> {
private Label label;
public UpdateLabel(Label label){
this.label = label;
}
@Override
protected Task<Void> createTask(){
return new Task<Void>(){
@Override
protected Void call(){
Platform.runLater(() -> {
/* Modify you GUI properties... */
label.setText(new Random().toString());
});
return null;
}
}
}
}
А затем используйте его:
class WindowController implements Initializable {
private @FXML Label randomNumber;
@Override
public void initialize(URL u, ResourceBundle res){
var service = new UpdateLabel(randomNumber);
service.setPeriod(Duration.seconds(2)); // The interval between executions.
service.play()
}
}