Подтвердить что ты не робот

Периодическая фоновая задача 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?

4b9b3361

Ответ 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()
   }
}