JavaFX 并发 - 使用在线程中运行但挂起 UI 的任务

JavaFX Concurrency - Using a task, which runs in a thread, but hangs the UI

关于并发有很多问题和答案,我的可能与其他人类似,但对我来说它不是重复的,因为出于某种原因我一定遗漏了一些东西并希望得到一些建议...

我的问题更多,我需要第二双眼睛来指出我做错了什么,以便在后台线程中启用我的代码 运行,但也更新了 GUI,没有冻结它。

最初,使用线程中的任务将 PDF 文件上传到应用程序。

这很好用。

显示一个进度条,动画没有问题:

上传文件()

public void uploadFile(File fileToProcess) {

  fileBeingProcessed = fileToProcess;

  Task<Parent> uploadingFileTask = new Task<Parent>() {
    @Override
    public Parent call() {
      try {
        progressBarStackPane.setVisible(true);
        pdfPath = loadPDF(fileBeingProcessed.getAbsolutePath());
        createPDFViewer();
        openDocument();
      } catch (IOException ex) {
        java.util.logging.Logger.getLogger(MainSceneController.class.getName()).log(Level.SEVERE, null, ex);
      }
      return null;
    }
  };

  uploadingFileTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
    @Override
    public void handle(WorkerStateEvent event) {
      fileHasBeenUploaded = true;
      progressBarStackPane.setVisible(false);
      uploadFilePane.setVisible(false);
      tabPane.setVisible(true);

      /* This is where I am getting issue, more so in createThumbnailPanels() */

      setupThumbnailFlowPane();
      createThumbnailPanels();

      /****** ^^^^^^^^^^^^ ******/

    }
  });

  uploadingFileTask.setOnFailed(evt -> {
    uploadingFileTask.getException().printStackTrace(System.err);
    System.err.println(Arrays.toString(uploadingFileTask.getException().getSuppressed()));
  });

  Thread uploadingFileThread = new Thread(uploadingFileTask);
  uploadingFileThread.start();

}

文档上传后,将显示在允许用户查看文档的选项卡中。

有一个辅助选项卡,在上传后禁用,直到完成另一个名为 createThumbnailPanelsTask;

的任务

但是,在此任务 运行 之前,已创建缩略图面板的 FlowPane。这似乎没有问题,并且似乎不是 GUI 挂起的原因(这显然是 createThumbnailPanelsTask 中的一个循环,但为了清楚起见,我将显示 setupThumbnailFlowPane()):

setupThumbnailFlowPane()

public void setupThumbnailFlowPane() {
  stage = model.getStage();
  root = model.getRoot();

  secondaryTabScrollPane.setFitToWidth(true);
  secondaryTabScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);

  /**
  This will be removed from here when refactored but for now it is here,
  I don't think this is anything to do with my issue
  **/

  Set<Node> nodes = secondaryTabScrollPane.lookupAll(".scroll-bar");
  for (final Node node : nodes) {
    if (node instanceof ScrollBar) {
      ScrollBar sb = (ScrollBar) node;
      if (sb.getOrientation() == Orientation.VERTICAL) {
        sb.setUnitIncrement(30.0);
      }
      if (sb.getOrientation() == Orientation.HORIZONTAL) {
        sb.setVisible(false);
      }
    }
  }

  secondaryTab = new FlowPane();
  secondaryTab.setId("secondaryTab");
  secondaryTab.setBackground(new Background(new BackgroundFill(Color.LIGHTSLATEGRAY, new CornerRadii(0), new Insets(0))));
  secondaryTab.prefWidthProperty().bind(stage.widthProperty());
  secondaryTab.prefHeightProperty().bind(stage.heightProperty());
  secondaryTab.setPrefWrapLength(stage.widthProperty().intValue() - 150);
  secondaryTab.setHgap(5);
  secondaryTab.setVgap(30);
  secondaryTab.setBorder(new Border(new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.NONE, CornerRadii.EMPTY, new BorderWidths(8, 10, 20, 10))));
  secondaryTab.setAlignment(Pos.CENTER);
}

最后,createThumbnailPanels() 被调用,我认为这是我遇到问题的地方。

假设发生的情况是,在文件上传后,上传文件窗格被隐藏,显示查看器选项卡,还有辅助选项卡。

辅助选项卡此时已禁用,左侧还有一个加载图像(gif)。

预期的行为是 createThumbnailPanels() 任务将在后台 运行,并且在完成之前,该选项卡将保持禁用状态,但是在此期间,gif 图片会旋转,给人的印象是正在加载。

加载完成后,gif 将被删除,并启用选项卡,允许用户导航到它,并查看生成的缩略图面板。

这一切都有效,但是,如前所述,任务是挂起 GUI:

createThumbnailPanels()

public void createThumbnailPanels() {
  Task<Void> createThumbnailPanelsTask = new Task<Void>() {
    @Override
    public Void call() {
      if (model.getIcePdfDoc() != null) {
        numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
        for (int thumbIndex = 0; thumbIndex < numberOfPagesInDocument; thumbIndex++) {
          ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
          Thumbnail tn = new Thumbnail(tb);
          model.setThumbnailAt(tn, thumbIndex);
          eventHandlers.setMouseEventsForThumbnails(tb);

          /*
          I have added this in as I am under the impression that a task runs in a background thread,
          and then to update the GUI, I need to call this:
          */

          Platform.runLater(() -> {
            secondaryTab.getChildren().add(tb);
          });

        }

      }

      return null;
    }
  };

  createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
    @Override
    public void handle(WorkerStateEvent event) {

      /*
      Further GUI modification run in setOnSucceeded so it runs on main GUI thread(?)
      */
      secondaryTabScrollPane.setContent(secondaryTab);
      secondaryTab.setDisable(false);
      secondaryTab.setGraphic(null);

    }
  });

  createThumbnailPanelsTask.setOnFailed(evt -> {
    createThumbnailPanelsTask.getException().printStackTrace(System.err);
    System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
  });

  Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
  createThumbnailPanelsThread.start();
}

除 GUI 在创建面板时挂起外,一切正常。

创建后,可以再次控制 GUI,移除加载 gif,启用选项卡,用户可以导航到它并查看面板。

很明显,这里关于并发我缺少一些东西。

如前所述,我的印象是后台线程中有一个任务 运行,所以我有点困惑为什么它似乎没有这样做。再一次,显然我错过了什么。

关于并发,我读了又读又读,但似乎无法弄清楚我的方法哪里出了问题。我很想尝试使用服务,但是,我觉得考虑到这一点我只是把事情复杂化了,而且显然有一种简单的方法可以实现我想要实现的目标。

任何帮助将不胜感激...推动正确的方向,或对我理解错误的地方进行一些澄清。

提前致谢,毫无疑问,一旦排序就可以帮助我避免以后出现这个问题!

更新代码

createThumbnailPanels()

 public void createThumbnailPanels() {
        Task<Void> createThumbnailPanelsTask = new Task<Void>() {
            //TODO: Need to check that it's a PDF
            @Override
            public Void call() {
                if (model.getIcePdfDoc() != null) {
                    numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
                    for (int thumbIndex= 0; thumbIndex< numberOfPagesInDocument; thumbIndex++) {
                        ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
                        Thumbnail tn = new Thumbnail(tb);                        
                        eventHandlers.setMouseEventsForThumbnails(tb);       
                        model.setThumbnailAt(tn, thumbIndex);
                        model.setThumbnailPanels(tb);
                    }
                    setThumbnailPanelsToScrollPane();
                }
                return null;
            }
        };

        createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
            @Override
            public void handle(WorkerStateEvent event) {
//              setThumbnailPanelsToScrollPane();
            }
        });

        createThumbnailPanelsTask.setOnFailed(evt -> {

createThumbnailPanelsTask.getException().printStackTrace(System.err);
            System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
        });

        Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
        createThumbnailPanelsThread.start();
    }

setThumbnailPanelsToScrollPane()

 public void setThumbnailPanelsToScrollPane() {
         Task<Void> setThumbnailPanelsToScrollPaneTask = new Task<Void>() {
            //TODO: Need to check that it's a PDF
            @Override
            public Void call() {
                 Platform.runLater(() -> {                     
                    secondaryTab.getChildren().addAll(model.getThumbnailPanels());
                    secondaryTabScrollPane.setContent(main.informationExtractionPanel);
                    secondaryTab.setDisable(false);
                    secondaryTab.setGraphic(null);
                  });   

                return null;
            }
        };


        setThumbnailPanelsToScrollPaneTask.setOnFailed(evt -> {
                setThumbnailPanelsToScrollPaneTask.getException().printStackTrace(System.err);
            System.err.println(Arrays.toString(setThumbnailPanelsToScrollPaneTask.getException().getSuppressed()));
        });

        Thread setThumbnailPanelsToScrollPaneThread = new Thread(setThumbnailPanelsToScrollPaneTask);
        setThumbnailPanelsToScrollPaneThread.start();
    }

仅供参考:如果我在 setOnSucceeded 中调用 setThumbnailPanelsToScrollPane();,它似乎不起作用。

getChildren().addJavaFX GUI 线程上 运行ning(这就是 Platform.runLater 所做的),但它只需要 运行 它在 Platform.runLater 如果你添加子项的父项连接到显示的 gui 的根,这意味着你应该能够将子项添加到未连接到任何根的父项,并且在子添加过程结束时将整个父添加到根,如果您在任何异步代码中执行 Platform.runLater,它将在 gui 线程上 运行 在您的情况下,它在您的异步 for 循环添加 ThumbnailPanels 如果它们的数量很大,gui 将挂起。