JavaFX периодическое фоновое задание - PullRequest
54 голосов
/ 01 апреля 2012

Я пытаюсь периодически запускать в фоновом потоке приложения JavaFX, который изменяет некоторые свойства GUI.

Я думаю, что знаю, как использовать Task и Service классы из javafx.concurrent, и не могу понять, как запустить такую ​​периодическую задачу без использования Thread#sleep() метода. Было бы неплохо, если бы я мог использовать Executor из 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 классы?

Ответы [ 4 ]

93 голосов
/ 01 апреля 2012

Вы можете использовать Временную шкалу, для чего:

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);
11 голосов
/ 08 января 2016

Я бы предпочел PauseTransition:

    PauseTransition wait = new PauseTransition(Duration.seconds(5));
    wait.setOnFinished((e) -> {
        /*YOUR METHOD*/
        wait.playFromStart();
    });
    wait.play();
6 голосов
/ 04 июня 2014

Вот решение с использованием 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() {
    // ...
}

По сравнению с решением Сергея вы не выделяете весь поток для получения статуса из сети, а вместо этого используете общий пул потоковза это.

1 голос
/ 06 июня 2019

Вы также можете использовать 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()
   }
}
...