Многопоточность JavaFX — присоединяющиеся потоки не обновят пользовательский интерфейс

#java #multithreading #javafx #javafx-2 #task

#java #многопоточность #javafx #javafx-2 #задача

Вопрос:

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

Но в таком случае мне нужно join() запустить поток синтаксического анализа, а перед продолжением перейти к основному, и это делает диалоговое окно пустым.

ParserTask.java

 public class ParserTask extends Task<Void>{
    @Override
    protected Void call() throws Exception {
        for(int i=0;i<10;i  ){
            //parse stuff
            Thread.sleep(1000);
            updateProgress(i,9);
            updateMessage("foo no:"  i);
        }
        updateMessage("done");
        return null;
    }

}
  

Main.java

 import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main  extends Application {

    @Override
    public void start(Stage stage) {
        Group root = new Group();
        Scene scene = new Scene(root, 300, 150);
        stage.setScene(scene);
        stage.setTitle("Progress Controls");
        Button btn = new Button("call the thread");
        btn.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                threadDialog();
            }
        });
        VBox vb = new VBox();
        vb.getChildren().addAll(btn);
        scene.setRoot(vb);
        stage.show();
    }
    public static void main(String[] args) {
        launch(args);
    }
    private void threadDialog() {
        Stage stageDiag = new Stage();
        ParserTask pTask = new ParserTask();
        final Label loadingLabel = new Label("");
        loadingLabel.textProperty().bind(pTask.messageProperty());
        final ProgressBar pbar = new ProgressBar();
        pbar.setProgress(0);
        pbar.setPrefHeight(20);
        pbar.setPrefWidth(450);
        pbar.progressProperty().bind(pTask.progressProperty());
        GridPane grid = new GridPane();
        grid.setHgap(14.0);
        grid.add(loadingLabel, 0, 0);
        grid.add(pbar, 1, 1);
        Scene sceneDiag = new Scene(grid);
        stageDiag.setScene(sceneDiag);
        stageDiag.setTitle("Foo thread is loading");
        stageDiag.show();
        Thread parser = new Thread(pTask);
        parser.start();
        try {
            parser.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  

Если я прокомментирую / удалю этот код:

         try {
            parser.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
  

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

     while(!loadingLabel.getText().contains("one")){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  

И даже while(parser.isAlive()); безуспешно.

Мой вопрос: как я могу дождаться своего потока и сохранить пользовательский интерфейс в рабочем состоянии?

Комментарии:

1. Примерно в миллионный раз повторяю, никогда не ждите, что будет в потоках событий GUI-hanler.

Ответ №1:

Вы никогда не должны блокировать поток пользовательского интерфейса.

Типичный шаблон для фоновой обработки выглядит следующим образом:

  1. Измените пользовательский интерфейс, чтобы указать, что выполняется фоновая обработка (показать индикатор выполнения, отключить кнопки и т.д.)
  2. Запустите фоновый поток
  3. В фоновом потоке после завершения обработки измените пользовательский интерфейс обратно и отобразите результаты обработки. Код, который это делает, должен вызываться с помощью Platform.runLater() (а также любой другой код, который взаимодействует с пользовательским интерфейсом из фоновых потоков)

Упрощенный пример:

 btn.setOnAction(new EventHandler<ActionEvent>(){
    @Override
    public void handle(ActionEvent arg0) {
        // 1
        setBackgroundProcessing(true); 

        // 2
        new Thread() {
            public void run() {
                doProcessing();

                // 3
                Platform.runLater(new Runnable() {
                    public void run() {
                        displayResults();
                        setBackgroundProcessing(false);
                    }
                });
            }
        }.start();
    }
});
  

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