JavaFX Запуск большого количества таймеров обратного отсчета за раз? - PullRequest
0 голосов
/ 03 декабря 2018

Таким образом, я вижу несколько разных способов сделать то, что мне нужно, и я сделал кучу поисков переполнения Google / стека, но не могу найти то, что я ищу.Мне нужно запустить несколько «таймеров обратного отсчета».Мне нужно иметь около 6, возможно, до 10 таймеров обратного отсчета, работающих одновременно в разное время.У меня есть панель вкладок в моей основной программе, в которую я включаю FXML и внедряю контроллеры.Вкладка «Таймеры» имеет контроллер, отличный от основной программы.

Итак, первый вопрос, который у меня есть.Поскольку эта «вкладка» работает на отдельном контроллере, но включена в основную программу, она запускается в отдельном потоке приложения?

Вот пример того, как выглядит включенная вкладка FXML ...

Countdown timers

Когда я нажимаю каждую кнопку запуска.Я могу создать Timeline и KeyFrame для каждого таймера.Тем не менее, я не думаю, что это лучший способ сделать это.Особенно, если вы одновременно запускаете до 10 временных шкал, и определенно, если это не выполняется в отдельном потоке приложения из основной программы.

Я думал об отправке каждого запроса на запуск в ExecutorService и newCacheThreadPool, однако мне нужно иметь возможность обновлять метки в графическом интерфейсе с оставшимся текущим временем, и я понимаю, что вы не должны этого делатьс фоновыми услугами.Platform.runLater() может быть?

Другая идея заключалась в использовании Timer из класса java.util.Timer.Тем не менее, я вижу, что возникают те же проблемы, что и ExecutorService, когда мне нужно обновить метки графического интерфейса.Я также понимаю, что класс Timer создает только один поток и выполняет свои задачи последовательно.Так что это не сработает.

Или, если у меня есть целый другой класс "CountDown", я могу создавать новые экземпляры для каждого из них, а затем запускать новые потоки. Однако, если я это сделаю,как мне постоянно обновлять графический интерфейс.Я все еще должен был бы опросить класс CountDown, используя timeline правильно?Так что это победит цель всего этого.

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

Спасибо,

Ответы [ 3 ]

0 голосов
/ 03 декабря 2018

Чтобы добавить к хорошему ответу , опубликованному Jai, вы можете протестировать различные реализации на производительность и выяснить, используют ли они отдельные потоки с помощью простой распечатки:

import java.io.IOException;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.PauseTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class TimersTest extends Application {

    @Override public void start(Stage stage) throws IOException {

        System.out.println("Fx thread id "+ Thread.currentThread().getId());

        VBox root = new VBox(new TimeLineCounter(), new PauseTransitionCounter(), new TaskCounter());
        stage.setScene(new Scene(root));
        stage.show();
    }

    public static void main(String[] args) { launch(args); }
}

abstract class Counter extends Label {

    protected int count = 0;
    public Counter() {
        setAlignment(Pos.CENTER); setPrefSize(25, 25);
        count();
    }

    abstract void count();
}

class TimeLineCounter extends Counter {

    @Override
    void count() {

        Timeline timeline = new Timeline();
        timeline.setCycleCount(Animation.INDEFINITE);
        KeyFrame keyFrame = new KeyFrame(
                Duration.seconds(1),
                event -> {  setText(String.valueOf(count++) );  }
        );
        timeline.getKeyFrames().add(keyFrame);
        System.out.println("TimeLine thread id "+ Thread.currentThread().getId());
        timeline.play();
    }
}

class PauseTransitionCounter extends Counter {

    @Override
    void count() {

        PauseTransition pauseTransition = new PauseTransition(Duration.seconds(1));
        pauseTransition.setOnFinished(event ->{
            setText(String.valueOf(count++) );
            pauseTransition.play();
        });
        System.out.println("PauseTransition thread id "+ Thread.currentThread().getId());
        pauseTransition.play();
    }
}

class TaskCounter extends Counter {

    @Override
    void count() { count(this); }

    void count(final Label label) {

         Task<Void> counterTask = new Task<>() {
                @Override
                protected Void call() throws Exception {
                    try {
                        System.out.println("Task counter thread id "+ Thread.currentThread().getId());
                        while(true){
                            Platform.runLater(() -> label.setText(String.valueOf(count++)));
                            Thread.sleep(1000);
                        }
                    } catch (InterruptedException e) {e.printStackTrace();     }
                    return null;
                }
            };

            Thread th = new Thread(counterTask);   th.setDaemon(true);    th.start();
    }
}

Распечаткакак и ожидалось, Timeline и PauseTransition находятся в потоке FX, а Task - нет:

идентификатор потока Fx 15Таймлайн поток с идентификатором 15Идентификатор потока PauseTransition 15Идентификатор потока счетчика задач 19

0 голосов
/ 03 декабря 2018

То, что вы ищете, это RxJava и его мост к JavaFx, который называется RxJavaFx.Импорт зависимости:

<dependency>
    <groupId>io.reactivex.rxjava2</groupId>
    <artifactId>rxjavafx</artifactId>
    <version>2.2.2</version>
</dependency>

и запуск

import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.rxjavafx.observables.JavaFxObservable;
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import io.reactivex.schedulers.Schedulers;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TimersApp extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {

        VBox vBox = new VBox();
        for (int i = 0; i < 4; i++) {
            ToggleButton button = new ToggleButton("Start");
            Label label = new Label("0");
            HBox hBox = new HBox(button, label, new Label("seconds"));
            vBox.getChildren().add(hBox);

            JavaFxObservable.valuesOf(button.selectedProperty())
            .switchMap(selected -> {
                if (selected) {
                    button.setText("Stop");
                    return Observable.interval(1, TimeUnit.SECONDS, Schedulers.computation()).map(next -> ++next);
                } else {
                    button.setText("Start");
                    return Observable.empty();
                }
            })
            .map(String::valueOf)
            .observeOn(JavaFxScheduler.platform())
            .subscribe(label::setText);
        }

        stage.setScene(new Scene(vBox));
        stage.show();
    }
}

Дайте мне знать, если вы заинтересованы в этом решении.Я предоставлю вам некоторые материалы для изучения.

0 голосов
/ 03 декабря 2018

Итак, первый вопрос, который у меня есть.Поскольку эта «вкладка» работает на отдельном контроллере, но включена в основную программу, она работает в отдельном потоке приложения?

Нет, может быть только one Экземпляр приложения JavaFX для JVM, а также один Поток приложения JavaFX для JVM.

Что касается того, как можно обновить таймер, то можно использовать Timeline - по одному для каждого таймера.Timeline не запускается в отдельном потоке - он запускается базовым «импульсом рендеринга графа сцены», который отвечает за периодическое обновление графического интерфейса JavaFX.Наличие большего числа Timeline экземпляров в основном означает, что есть больше слушателей, которые подписываются на событие «pulse».

public class TimerController {
    private final Timeline timer;

    private final ObjectProperty<java.time.Duration> timeLeft;

    @FXML private Label timeLabel;

    public TimerController() {
        timer = new Timeline();
        timer.getKeyFrames().add(new KeyFrame(Duration.seconds(1), ae -> updateTimer()));
        timer.setCycleCount(Timeline.INDEFINITE);

        timeLeft = new SimpleObjectProperty<>();
    }
    public void initialize() {
        timeLabel.textProperty().bind(Bindings.createStringBinding(() -> getTimeStringFromDuration(timeLeft.get()), timeLeft));
    }

    @FXML
    private void startTimer(ActionEvent ae) {
        timeLeft.set(Duration.ofMinutes(5)); // For example timer of 5 minutes
        timer.playFromStart();
    }

    private void updateTimer() {
        timeLeft.set(timeLeft.get().minusSeconds(1));
    }

    private static String getTimeStringFromDuration(Duration duration) {
        // Do the conversion here...
    }
}

Конечно, вы также можете использовать Executor и другие методы потоков, если вы обновляетеLabel через Platform.runLater().В качестве альтернативы вы можете использовать Task.

. Это общий пример использования фонового потока:

final Duration countdownDuration = Duration.ofSeconds(5);
Thread timer = new Thread(() -> {
    LocalTime start = LocalTime.now();
    LocalTime current = LocalTime.now();
    LocalTime end = start.plus(countDownDuration);

    while (end.isAfter(current)) {
        current = LocalTime.now();
        final Duration elapsed = Duration.between(current, end);

        Platform.runLater(() -> timeLeft.set(current)); // As the label is bound to timeLeft, this line must be inside Platform.runLater()
        Thread.sleep(1000);
    }
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...