【问题标题】:Execute task in background in JavaFX在JavaFX的后台执行任务
【发布时间】:2012-02-28 05:33:08
【问题描述】:

我想在 TilePane 中加载多达九个面板。对于每个窗格,我必须首先运行内容计算(大约 300 毫秒),然后我必须构建面板(大约 500 毫秒)。

我想要的是,有九个 ProgressIndicators 在计算后与每个面板进行交换。

我使用Platform.runLater 命令以及服务类进行了尝试。结果总是一样的。 ProgressIndicator 显示,但没有动画。几秒钟后,所有面板同时出现。

有没有可能,指标一直是动画的,我可以一个接一个地交换它们?

【问题讨论】:

    标签: service task javafx-2


    【解决方案1】:

    JavaFX 有用于 UI 事件的事件调度线程。所有与 UI 相关的工作都应该在这个线程上进行。并且不应在此处进行非 UI 计算以避免 UI 滞后。

    查看下一个代码:

    public class Indicators extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) {
            Pane root = new HBox();
            stage.setScene(new Scene(root, 300, 100));
    
            for (int i = 0; i < 10; i++) {
                final ProgressIndicator pi = new ProgressIndicator(0);
                root.getChildren().add(pi);
    
                // separate non-FX thread
                new Thread() {
    
                    // runnable for that thread
                    public void run() {
                        for (int i = 0; i < 20; i++) {
                            try {
                                // imitating work
                                Thread.sleep(new Random().nextInt(1000));
                            } catch (InterruptedException ex) {
                                ex.printStackTrace();
                            }
                            final double progress = i*0.05;
                            // update ProgressIndicator on FX thread
                            Platform.runLater(new Runnable() {
    
                                public void run() {
                                    pi.setProgress(progress);
                                }
                            });
                        }
                    }
                }.start();
            }
    
            stage.show();
    
        }
    }
    

    【讨论】:

    • 嗨 Sergey...我有一个问题,我可以在应用程序中运行几个这样的线程吗?谢谢。
    • 感谢 Sergey,建议在应用程序中有几个 Platform.runLater??。由于这些线程必须改变视图,例如是否显示对话框。
    • @MarcoR 当然,除了在那些 runLater 调用中更新 UI 之外不要做任何事情,因为只有一个 UI 线程,并且在 runLater 完成之前不会发生任何事情。
    【解决方案2】:

    这就是我解决问题的方法:

    import java.util.Random;
    
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.beans.value.ChangeListener;
    import javafx.beans.value.ObservableValue;
    import javafx.concurrent.Service;
    import javafx.concurrent.Task;
    import javafx.concurrent.Worker;
    import javafx.concurrent.Worker.State;
    import javafx.geometry.Insets;
    import javafx.scene.Group;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.control.ProgressBar;
    import javafx.scene.control.ProgressIndicator;
    import javafx.scene.layout.StackPane;
    import javafx.scene.layout.TilePane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.stage.Stage;
    
    
    public class Minimal extends Application {
    
    private TilePane loadPane;
    private ProgressIndicator[] indicators = new ProgressIndicator[9];
    private Label loading[] = new Label[9];
    private Color[] colors = {Color.BLACK,Color.BLUE,Color.CRIMSON,Color.DARKCYAN,Color.FORESTGREEN,Color.GOLD,Color.HOTPINK,Color.INDIGO,Color.KHAKI};
    private int counter = 0;
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        //creating Layout
        final Group root = new Group();                             
        Scene scene = new Scene(root, 400, 400);            
        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        StackPane waitingPane = new StackPane();
        final ProgressBar progress = new ProgressBar();
        Label load = new Label("loading things...");
        progress.setTranslateY(-25);
        load.setTranslateY(25);
        waitingPane.getChildren().addAll(new Rectangle(400,400,Color.WHITE),load,progress);
        root.getChildren().add(waitingPane);
    
        //Task for computing the Panels:
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                for (int i = 0; i < 20; i++) {
                    try {
                        Thread.sleep(new Random().nextInt(1000));
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    final double prog = i*0.05;
                    Platform.runLater(new Runnable() {
                        public void run() {
                            progress.setProgress(prog);
                        }
                    });
                }
                return null;
            }
        };
    
        //stateProperty for Task:
        task.stateProperty().addListener(new ChangeListener<Worker.State>() {
    
            @Override
            public void changed(ObservableValue<? extends State> observable,
                    State oldValue, Worker.State newState) {
                if(newState==Worker.State.SUCCEEDED){
                    loadPanels(root);
                }
            }
        });
    
        //start Task
        new Thread(task).start();
    
        primaryStage.show();
    }
    
    private void loadPanels(Group root) {
        //change to loadPanel:
        root.getChildren().set(0,createLoadPane());
    
        //Service:
        final Service<Rectangle> RecBuilder = new Service<Rectangle>() {
              @Override protected Task<Rectangle> createTask() {
                  return new Task<Rectangle>() {
                      @Override protected Rectangle call() throws InterruptedException {
                          updateMessage("loading rectangle . . .");
                          updateProgress(0, 10);
                          for (int i = 0; i < 10; i++) {
                            Thread.sleep(100);
                          }
                          updateMessage("Finish!");
                          return new Rectangle((380)/3,(380)/3,colors[counter]);
                      }
                  };
              }
        };
    
        //StateListener
        RecBuilder.stateProperty().addListener(new ChangeListener<Worker.State>() {
            @Override public void changed(ObservableValue<? extends Worker.State> observableValue,
                            Worker.State oldState, Worker.State newState) {
                switch (newState) {
                case SCHEDULED:
                    break;
                case READY:
                case RUNNING:
                    break;
                case SUCCEEDED:
                    Rectangle rec = RecBuilder.valueProperty().getValue();
                    indicators[counter].progressProperty().unbind();
                    loading[counter].textProperty().unbind();
                    loadPane.getChildren().set(counter, rec);
                    if(counter<8){
                        counter++;
                        nextPane(RecBuilder);
                    }
                    break;
                case CANCELLED:
                case FAILED:
                    loading[counter].textProperty().unbind();
                    loading[counter].setText("Failed!");
                    if(counter<8){
                        counter++;
                        nextPane(RecBuilder);
                    }
                    break;
                }
            }
        });
    
        //begin PanelBuilding:
        nextPane(RecBuilder);
    }
    
    private void nextPane(Service<Rectangle> recBuilder) {
        loading[counter].textProperty().bind(recBuilder.messageProperty());
        indicators[counter].visibleProperty().bind(recBuilder.progressProperty().isNotEqualTo(new SimpleDoubleProperty(ProgressBar.INDETERMINATE_PROGRESS)));
        recBuilder.restart();
    }
    
    private Node createLoadPane() {
        loadPane = new TilePane(5,5);
        loadPane.setPrefColumns(3);
        loadPane.setPadding(new Insets(5));
        for(int i=0;i<9;i++){
            StackPane waitingPane = new StackPane();
            Rectangle background = new Rectangle((380)/3, (380)/3, Color.WHITE);
            indicators[i] = new ProgressIndicator();
            indicators[i].setPrefSize(50, 50);
            indicators[i].setMaxSize(50, 50);
            indicators[i].setTranslateY(-25);
            indicators[i].setTranslateX(-10);
            loading[i] = new Label();
            loading[i].setTranslateY(25);
            waitingPane.getChildren().addAll(background,indicators[i],loading[i]);
            loadPane.getChildren().add(waitingPane);
        }
    
        return loadPane;
    }
    
    public static void main(String[] args) {
        launch(args);
    }
    
    }
    

    【讨论】:

    • 谢谢。我认为问题在于一个任务(最好是单个任务实例)不能多次运行。将任务实例放在一个方法中,让我们通过每个方法调用重新初始化它,也为我解决了这个问题。不错:)
    【解决方案3】:

    最简单的方法是使用 lambda 和线程。

    private void logIn() {
        new Thread(() -> {
            //CONNECT TO WEB AND LOG IN (ON OUT OF FX THREAD)
            boolean loginSuccess = new LogIn(emial.getText(), pass.getText()).execute();
    
            //DO SOMETHING WITH CONTROLLS ON FX THREAD ACCORDING RESULT OF OVER
            Platform.runLater(() -> {
                if (loginSuccess) {
                    info.setText("SUCCESS");
                    info.setTextFill(Color.web("#268515"));
                } else {
                    info.setText("FAIL");
                    info.setTextFill(Color.web("#CD000E"));
                }
            });
        }).start();
    }
    

    【讨论】:

      【解决方案4】:

      这个问题的其他答案似乎没有使用专门为此目的制作的 Task 类。确实使用它的答案,我觉得在某些地方没有遵循最佳实践。

      这是我对 Yggdrasil 答案的整理(我认为这是迄今为止最好的)。具体变化如下:

      • 当 Task 已经具有带有 updateProgress 方法的进度属性时,不要使用 Platform.runLater 从 Task 的调用方法中更新进度)
      • 覆盖 Task 的成功和失败方法,而不是添加状态监听器
      • 尝试将状态封装在任务中,例如它正在计算的正方形的索引
      • 修复了导致其他所有矩形计算失败的状态问题
      • 添加了随机失败以显示失败任务的 UI 更新
      • 修复了基于服务的任务中的进度更新
      • 对布局代码的小幅调整
      import java.util.Random;
      
      import javafx.concurrent.Service;
      import javafx.concurrent.Task;
      import javafx.geometry.Insets;
      import javafx.scene.Group;
      import javafx.scene.Node;
      import javafx.scene.Scene;
      import javafx.scene.control.Label;
      import javafx.scene.control.ProgressBar;
      import javafx.scene.control.ProgressIndicator;
      import javafx.scene.layout.StackPane;
      import javafx.scene.layout.TilePane;
      import javafx.scene.paint.Color;
      import javafx.scene.shape.Rectangle;
      
      import javafx.application.Application;
      import javafx.geometry.Pos;
      import javafx.scene.control.Tooltip;
      import javafx.scene.layout.VBox;
      import javafx.stage.Stage;
      
      public class AsyncProgress extends Application {
          static final Random rng = new Random();
          private Group root;
          private ProgressBar progress;
          private TilePane loadPane;
          private ProgressIndicator[] indicators = new ProgressIndicator[9];
          private Label loading[] = new Label[9];
          private Color[] colors = {Color.BLACK, Color.BLUE, Color.CRIMSON, Color.DARKCYAN, Color.FORESTGREEN, Color.GOLD, Color.HOTPINK, Color.INDIGO, Color.KHAKI};
          private int counter = 0;
      
          public static void main(String[] args) {
              launch(args);
          }
      
          @Override
          public void start(Stage primaryStage) throws Exception {
              //creating Layout
              root = new Group();
              Scene scene = new Scene(root, 400, 400);
              primaryStage.setScene(scene);
              primaryStage.setResizable(false);
              progress = new ProgressBar();
              Label load = new Label("loading things...");
              VBox waitingPane = new VBox(10, load, progress);
              waitingPane.setPrefSize(400, 400);
              waitingPane.setAlignment(Pos.CENTER);
              root.getChildren().add(waitingPane);
      
              //Task for computing the Panels:
              Task<Void> panelTask = new Task<Void>() {
                  @Override
                  protected Void call() throws Exception {
                      updateProgress(0, 0);
                      for (int i = 0; i < 20; i++) {
                          sleep(rng.nextInt(500));
                          updateProgress(i * 0.05, 1);
                      }
                      return null;
                  }
      
                  @Override
                  protected void succeeded() {
                      loadPanels();
                  }
      
              };
      
              progress.progressProperty().bind(panelTask.progressProperty());
      
              primaryStage.show();
      
              var t = new Thread(panelTask);
              t.setDaemon(true);
              t.start();
          }
      
          private void loadPanels() {
              progress.progressProperty().unbind();
              root.getChildren().set(0, createLoadPane());
      
              final Service<Rectangle> recBuilderService = new Service<Rectangle>() {
                  @Override
                  protected Task<Rectangle> createTask() {
                      return new Task<Rectangle>() {
                          private int squareIndex = counter++;
      
                          @Override
                          protected Rectangle call() {
                              updateMessage("loading rectangle . . .");
                              updateProgress(0, 10);
                              for (int i = 0; i < 10; i++) {
                                  sleep(100);
                                  if (rng.nextDouble() > 0.95) {
                                      throw new RuntimeException("Rectangle failed to load.");
                                  }
                                  updateProgress(i + 1, 10);
                              }
                              updateMessage("Finish!");
                              return new Rectangle((380) / 3, (380) / 3, colors[squareIndex]);
                          }
      
                          @Override
                          protected void succeeded() {
                              setRectangle(squareIndex, getValue());
                          }
      
                          @Override
                          protected void failed() {
                              failRectangle(squareIndex, getException());
                          }
                      };
                  }
              };
      
              recBuilderService.stateProperty().addListener((observableValue, oldState, newState) -> {
                  switch (newState) {
                      case SUCCEEDED:
                      case FAILED:
                          if (counter <= 8) {
                              nextPane(recBuilderService);
                          }
                      default:
                          break;
                  }
              });
      
              // kickstart the panel building
              nextPane(recBuilderService);
          }
      
          private void nextPane(Service<Rectangle> recBuilder) {
              loading[counter].textProperty().bind(recBuilder.messageProperty());
              var prog = indicators[counter];
              prog.visibleProperty().bind(prog.progressProperty().isNotEqualTo(ProgressBar.INDETERMINATE_PROGRESS, 0));
              prog.progressProperty().bind(recBuilder.progressProperty());
              recBuilder.restart();
          }
      
          private void failRectangle(int index, Throwable reason) {
              var tt = new Tooltip(reason.getMessage());
              var msg = loading[index];
              msg.setTooltip(tt);
              msg.textProperty().unbind();
              msg.setText("Failed!");
              var ind = indicators[index];
              ind.progressProperty().unbind();
              ind.setTooltip(tt);
          }
      
          private void setRectangle(int index, Rectangle rec) {
              indicators[index].progressProperty().unbind();
              var lab = loading[index];
              lab.textProperty().unbind();
              loadPane.getChildren().set(index, rec);
          }
      
          private Node createLoadPane() {
              loadPane = new TilePane(5, 5);
              loadPane.setPrefColumns(3);
              loadPane.setPadding(new Insets(5));
              for (int i = 0; i < 9; i++) {
                  Rectangle background = new Rectangle(380 / 3, 380 / 3, Color.WHITE);
                  indicators[i] = new ProgressIndicator();
                  indicators[i].setPrefSize(50, 50);
                  indicators[i].setMaxSize(50, 50);
                  loading[i] = new Label();
                  loading[i].setTranslateY(35);
                  StackPane waitingPane = new StackPane(background, indicators[i], loading[i]);
                  waitingPane.setAlignment(Pos.CENTER);
                  loadPane.getChildren().add(waitingPane);
              }
      
              return loadPane;
          }
      
          static void sleep(long ms) {
              try {
                  Thread.sleep(ms);
              } catch (InterruptedException ex) {
                  Thread.currentThread().interrupt();
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-06-29
        • 2013-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多