JavaFX Concurrency - использование задачи, которая выполняется в потоке, но зависает - PullRequest
0 голосов
/ 14 января 2019

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

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

Первоначально файл 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 для панелей миниатюр. Кажется, это работает без проблем и не является причиной зависания графического интерфейса (это явно цикл в 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(), и я полагаю, что у меня проблема.

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

В этот момент дополнительная вкладка отключена, и на левой ее стороне также находится изображение загрузки (a gif).

Предполагается, что задача createThumbnailPanels() будет выполняться в фоновом режиме, и до ее завершения вкладка будет оставаться отключенной, однако в течение этого времени изображение gif будет вращаться, создавая впечатление происходит некоторая загрузка.

После завершения загрузки gif удаляется, и вкладка активируется, позволяя пользователю перейти к ней и просмотреть сгенерированные панели миниатюр.

Это все работает, однако, как уже упоминалось, задача вешает графический интерфейс:

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

Все, кроме зависания графического интерфейса, пока он создает панели, работает нормально.

После того, как они были созданы, графический интерфейс можно снова контролировать, загрузка 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();
    }

К вашему сведению: если я позвоню setThumbnailPanelsToScrollPane(); в setOnSucceeded, он не будет работать.

1 Ответ

0 голосов
/ 14 января 2019

getChildren().add выполняется в потоке графического интерфейса JavaFX (это то, что делает Platform.runLater), но требуется только для запуска его в Platform.runLater, если родитель, к которому вы добавляете потомков, подключен к корню показанного графического интерфейса, это означает, что вы должны иметь возможность добавлять дочерние элементы к родителю, который не подключен ни к какому корню, и добавлять весь родительский элемент к корню в конце процесса добавления дочерних элементов, если вы делаете Platform.runLater в любом асинхронном коде он будет выполняться в потоке графического интерфейса, в вашем случае он находится в асинхронном цикле for с добавлением ThumbnailPanels и, если их число велико, графический интерфейс будет зависать.

...