Какие операции я должен поместить в Platform.runLater ()? - PullRequest
0 голосов
/ 02 октября 2019

В последнее время я столкнулся с некоторыми проблемами с кодом, который асинхронно обновляет графический интерфейс. Затем я наткнулся на эту статью , которая пролила некоторый свет на проблему - я не использовал Platform.runLater() для обновления компонентов GUI, однако рассмотрим оригинальный код:

public class Main extends Application {
    private TextArea textArea = new TextArea();
    private Label statusLabel = new Label("Not Started...");
    private Button startButton = new Button("Start");
    private Button exitButton = new Button("Exit");

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

    @Override
    public void start(final Stage stage) {
        startButton.setOnAction(event -> startTask());
        exitButton.setOnAction(event -> stage.close());
        HBox buttonBox = new HBox(5, startButton, exitButton);
        VBox root = new VBox(10, statusLabel, buttonBox, textArea);
        root.setStyle("-fx-padding: 10;" +
                "-fx-border-style: solid inside;" +
                "-fx-border-width: 2;" +
                "-fx-border-insets: 5;" +
                "-fx-border-radius: 5;" +
                "-fx-border-color: blue;");
        Scene scene = new Scene(root, 400, 300);
        stage.setScene(scene);
        stage.setTitle("A simple Concurrency Example");
        stage.show();
    }

    private void startTask() {
        Runnable task = this::runTask;
        Thread backgroundThread = new Thread(task);
        backgroundThread.setDaemon(true);
        backgroundThread.start();
    }

    private void runTask() {
        for (int i = 1; i <= 10; i++) {
            try {
                String status = "Processing " + i + " of " + 10;
                statusLabel.setText(status);
                textArea.appendText(status + "\n");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Проблемной частью является метод runTask(). В статье объясняется, что вместо простого использования statusLabel.setText(status), я должен использовать Platform.runLater(() -> statusLabel.setText(status));. Это имеет смысл для меня.

Что не имеет смысла, однако, почему я не должен применять ту же логику к textArea обновлениям? Обратите внимание на пятую строку метода runTask() - часть textArea.appendText(status + "\n");. Почему это не дает мне исключения (java.lang.IllegalStateException: Not on FX application thread) при модификации компонента FX из потока, не относящегося к FX, поскольку это явно способ обновления компонента FX графического интерфейса пользователя? Какие операции я должен поместить в Platform.runLater() и какие операции теперь должны быть там?

1 Ответ

0 голосов
/ 02 октября 2019

Запуск позже будет более или менее влиять на поток пользовательского интерфейса. Выполнение будет поставлено в очередь в потоке пользовательского интерфейса. Когда у вас будет спящий поток или долгий расчет, пользовательский интерфейс будет зависать. Вы можете использовать его для простых задач. Но в вашем случае я рекомендую это, потому что это всего лишь «операция набора».

Обычно вы должны использовать Наблюдаемые свойства и связывать их с пользовательским интерфейсом, когда это возможно. Тогда вам не нужно ничего делать с операциями «Thread», «Queuing» и «RunLater».

Когда для выполнения операции ВСЕ нужно «запустить позже», а расчет будет дорогостоящим, вам следует поискать, что можно запустить позже, или использовать другой механизм.

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

Итак, вы хотите обновить элементы пользовательского интерфейса, когда это необходимо, и новыйзначения вычисляются или предоставляются любым механизмом.

Здесь можно перейти к свойствам:

StringProperty text = new SimpleStringProperty("Hello");

Label label = new Label();

label.getTextProperty().bind(text);

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

Если иного пути нет:

Что вы могли бы (в случае более дорогостоящих операций) сделать, это иметь очередь, которая содержит все выполнения обновлений для представления, которые поступают асинхронно. И выполнять изменения только в потоке JavaFX.

Вы должны использовать Animationtimer для этого.

//thread safe queue
Queue<Runnable> queue = new LinkedBlockingQueue<Runnable>();

Теперь подход с runLater:

Platform.runLater(()->{
    while(!queue.isEmpty()) {
        queue.remove().run();
    }
});

И подход с AnimationTimer.

AnimationTimer timer = new AnimationTimer() {
    @Override
    public void handle(long now) {
        //Whatever condition or how many changes you would make at one tick
        //In this case we just run all "updates" in that tick.
        // I would recommend it, you could update 100 things each tick
        while(!queue.isEmpty()) {
            queue.remove().run();
        }

    }
};
timer.start();

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

private void startTask() {
    Runnable task = this::runTask;
    Thread backgroundThread = new Thread(task);
    backgroundThread.setDaemon(true);
    backgroundThread.start();
}

private void runTask() {
    for (int i = 1; i <= 10; i++) {
        try {
            String status = "Processing " + i + " of " + 10;
            //Add the new change as a Runnable here
            //It will be run in the in the next update of the UI 

            tasks.add(()->{
                statusLabel.setText(status);

                textArea.appendText(status + "\n");
           });

            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ПРИМЕЧАНИЕ

Все еще зависит, для каких целей вы собираетесь его использовать. Это всего лишь два подхода. Когда вы обновляете только объявленные значения, вы также можете использовать привязки свойств. (StringProperty в вашем случае)

И я бы лично всегда пошел с AnimationTimer. RunLater решает, когда он «в порядке» с очередью Runnable. С Animationtimer вы можете быть уверены, что он выполняется каждый тик, когда требуется обновление.

Вы также можете сохранять все обновления, а затем проводить опрос новых значений каждый тик.

ЗАДАЧА

Когда вы точно знаетегде вы хотите указать, какое значение вы должны использовать Task и связать его с элементом UI, это также будет работать.

Label statusLabel = new Label("Not Started...");

Task<String> t = new Task<String>() {

@Override
protected String call() throws Exception {
    int i = 0;
    while(//any condition ... ) {
        i++;
        Thread.sleep(1000);
        updateValue("i is " + i);
    }

    return "last value!";
}
// now Bind the TextProperty of the Label to the ValueProperty of the  
// Task
statusLabel.getTextproperty().bind(task.getValueProperty());
Thread backgroundThread = new Thread(task);
backgroundThread.start();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...