代码之家  ›  专栏  ›  技术社区  ›  Adam

javafx并发性-使用一个在线程中运行但挂起UI的任务

  •  0
  • Adam  · 技术社区  · 5 年前

    在并发性方面有很多问题和答案,我的问题和答案可能与其他问题类似,但对于我来说,这不是重复的,因为某些原因,我必须错过一些东西,并希望得到一些建议…

    我的问题更多的是,我需要第二双眼睛来指出我做的错误,以使我的代码能够在后台线程中运行,同时也更新了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挂起的原因(这显然是 创建缩略图面板标记 ,但为了清楚起见,我将展示 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() 这就是我认为我遇到问题的地方。

    假设发生的情况是,文档上载后,“上载文件”窗格隐藏,显示“查看器”选项卡和“辅助”选项卡。

    此时将禁用辅助选项卡,并且还具有加载图像(A gif )在它的左边。

    预期的行为是 创建缩略图面板() 任务将在后台运行,在完成之前,选项卡将保持禁用状态,但在此期间, GIF 图像将旋转,给人的印象是有一些负载发生。

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

    但是,正如前面提到的,这一切都可以工作,任务是挂起GUI:

    创建缩略图面板()

    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 已删除,选项卡已启用,用户可以导航到它并查看面板。

    清楚地 ,这里的并发性缺少一些东西。

    如前所述,我觉得任务是在后台线程中运行的,所以我有点困惑为什么它看起来不这样做。很明显,我错过了什么。

    我已经阅读、阅读和阅读了并发性,但似乎无法找出我的方法中哪里出错了。我很想尝试使用一个服务,但是,我觉得考虑到这一点,我觉得事情过于复杂了,显然有一种简单的方法可以实现我想要实现的目标。

    任何帮助都将不胜感激…向正确的方向推进,或者澄清我在理解上哪里出错了。

    事先谢谢,毫无疑问,这是一件很明显的事情,一旦排序将帮助我避免这个问题在未来!

    更新代码

    创建缩略图面板()

     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();
        }
    

    将缩略图面板设置为滚动窗格()。

     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();
        }
    

    如果我打电话 setThumbnailPanelsToScrollPane(); 在setonsucceed中,它似乎不起作用。

    1 回复  |  直到 5 年前
        1
  •  1
  •   мυѕτавєւмo    5 年前

    getChildren().add 正在运行 JavaFX GUI线程(这就是 Platform.runLater 但只需要在 平台.runlater 如果添加子对象的父对象连接到所示GUI的根,这意味着您应该能够将子对象添加到未连接到任何根的父对象,并在子对象添加过程结束时将整个父对象添加到根对象(如果您正在执行此操作)。 平台.runlater 在任何异步代码中,它都将在GUI线程上运行,在您的情况下,它将在异步代码中运行。 for 循环加法 ThumbnailPanels 如果它们的数量很大,GUI就会挂起。