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

时间:2019-01-14 10:14:54

标签: java multithreading javafx concurrency javafx-8

关于并发性有很多问题和答案,我的可能与其他人相似,但对我而言,这不是重复的,因为某些原因,我必须丢失某些东西并希望得到一些建议...

我的问题是,我需要第二只眼睛指出我在做什么,以使我的代码能够在后台线程中运行,而且还更新了GUI而不冻结它。

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

这很好。

显示进度条,其动画效果没有问题:

uploadFile()

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,启用了选项卡,并且用户可以导航到该视图并查看面板。

很显然,在这里我缺少关于并发性的东西。

如前所述,我的印象是Task在后台线程中运行,所以我对为什么它似乎没有这样做感到困惑。再次,显然我缺少了一些东西。

我已经阅读并阅读了有关并发的文章,但是似乎无法弄清我在哪里出错了。我很想尝试使用服务,但是,我认为考虑到这只是使事情复杂化了,而且显然有一种简单的方法可以实现我想要实现的目标。

任何帮助将不胜感激……朝着正确的方向推进,或者对我理解上的错误之处进行澄清。

在此先感谢您,毫无疑问,一旦整理,这将有助于我将来避免此问题!

更新代码

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();,它似乎不起作用。

1 个答案:

答案 0 :(得分:1)

getChildren().add JavaFX GUI线程上运行(这就是Platform.runLater的作用),但是只有在父级Platform.runLater中运行它才需要您将子代添加到的用户已连接到显示的gui的根,这意味着您应该能够将子代添加到未连接到任何根的父代,并在子代添加末尾将整个父代添加到根流程中,如果您在任何异步代码中执行Platform.runLater,它将在gui线程上运行(在您的情况下)是在您的异步for循环中添加ThumbnailPanels,并且它们的数量是大gui将挂起。