【问题标题】:How to properly thread off javafx Alerts/fileChooser etc如何正确关闭 javafx Alerts/fileChooser 等
【发布时间】:2018-10-08 14:29:49
【问题描述】:

我在看这个问题JavaFX show dialogue after thread task is completed,但我的问题恰恰相反。在需要用户返回一些数据的文件选择器或警报之后,最好的方法是什么?

这是我现在拥有的:

Platform.runLater(()->{
    File file = fileChooser.showOpenDialog(root.getScene().getWindow());
    if(file == null) {
        return;
    }
    executorService.execute(()->{
        //more code here which uses file
    });
});

其中 executorService 是之前创建的 ExecutorService。我想我可以很容易地使用 Task 或 Thread 或其他任何东西,但是它的线程化方式并不重要,只是这需要一段时间,我不想在 Application 线程上发生,因为它会锁定用户界面。

我知道这不是 mvce,但我希望它能够证明我在 Platform.runLater 调用中遇到的线程问题。

这是一个极端的例子,说明这种事情变得多么复杂

@FXML
public void copyFiles(ActionEvent event){
    //this method is on the application thread because a button or something started it
    // so we thread off here
    executorService.execute(()->{
        // do some stuff
        // ...
        // get location to copy to from user
        // must happen on the application thread!
        Platform.runLater(()->{
            File file = fileChooser.showOpenDialog(root.getScene().getWindow());
            if(file == null) {
                return;
            }
            executorService.execute(()->{
                // more code here which uses file
                // ...
                // oh wait, some files have the same names! 
                // we need a user's confirmation before proceeding
                Platform.runLater(()->{
                    Alert alert = new Alert(AlertType.CONFIRMATION, "Do you want to overwrite files with the same names?", ButtonType.OK, ButtonType.CANCEL);
                    Optional<ButtonType> choice = alert.showAndWait();
                    if(choice.isPresent && choice.get == ButtonType.OK){
                        // do something, but not on the application thread
                        executorService.execute(()->{
                            // do the last of the copying
                            // ...
                        });
                    }
                });
            });
        });
    });
}

【问题讨论】:

    标签: java multithreading javafx


    【解决方案1】:

    如果您需要在返回结果的 UI 线程上执行某些操作,请创建一个 FutureTask,将其提交给 UI 线程,然后在后台线程上等待它完成。这允许您“扁平化”代码。

    您还可以将Platform.runLater(...) 抽象为Executor(毕竟它只是执行Runnables 的东西),这可以使它(也许)稍微干净一些。

    通过分解成更小的方法(通常只使用其他标准编程技术),您可以使代码非常干净。

    这是基本思想(您需要添加异常处理,或者创建一个Callable(可以抛出异常)而不是Runnable):

    @FXML
    public void copyFiles(ActionEvent event){
    
        Executor uiExec = Platform::runLater ;
    
        //this method is on the application thread because a button or something started it
        // so we thread off here
    
        Callable<Void> backgroundTask = () -> {
            doFirstTimeConsumingThing();
    
            FutureTask<File> getUserFile = new FutureTask<>(this::getUserFile) ;
            uiExec.execute(getUserFile);
            File file = getUserFile.get();
            if (file == null) return null ;
    
            doAnotherTimeConsumingThing(file);
            FutureTask<Boolean> getUserConfirmation = new FutureTask<>(this::showConfirmation);
            uiExec.execute(getUserConfirmation);
            if (! getUserConfirmation.get()) return null ;
    
            doMoreTimeConsumingStuff();
    
            // etc...
    
            return null ;
        };
        executorService.execute(backgroundTask);
    }
    
    private File getUserFile() {
        return fileChooser.showOpenDialog(root.getScene().getWindow());
    }
    
    private Boolean getUserConfirmation() {
        Alert alert = new Alert(AlertType.CONFIRMATION, "Do you want to overwrite files with the same names?", ButtonType.OK, ButtonType.CANCEL);
        return alert.showAndWait()
            .filter(ButtonType.OK::equals)
            .isPresent();
    }
    
    private void doFirstTimeConsumingThing() {
        // ...
    }
    
    private void doAnotherTimeConsumingThing(File file) {
        // ....
    }
    
    private void doMoreTimeConsumingStuff() {
        // ...
    }
    

    【讨论】:

      【解决方案2】:

      您的问题似乎是在后台任务中需要信息,这些信息只能在 JavaFX 应用程序线程上检索。 James_D 给出的answer 非常适合使用FutureTask。我想提供一个替代方案:CompletableFuture(在 Java 8 中添加)。

      public void copyFiles(ActionEvent event) {
      
          executorService.execute(() -> {
      
              // This uses CompletableFuture.supplyAsync(Supplier, Executor)
      
              // need file from user
              File file = CompletableFuture.supplyAsync(() -> {
                  // show FileChooser dialog and return result
              }, Platform::runLater).join(); // runs on FX thread and waits for result
      
              if (file == null) {
                  return;
              }
      
              // do some stuff
      
              // ask for confirmation
              boolean confirmed = CompletableFuture.supplyAsync(() -> {
                  // show alert and return result
              }, Platform::runLater).join(); // again, runs on FX thread and waits for result
      
              if (confirmed) {
                  // do more stuff
              }
      
          });
      }
      

      FutureTaskCompletableFuture 都适合您。我更喜欢CompletableFuture,因为它提供了更多选项(如果需要)并且join() 方法不会像get() 那样抛出检查异常。但是,CompletableFutureFuture(就像 FutureTask),因此您仍然可以将 get()CompletableFuture 一起使用。

      【讨论】:

        猜你喜欢
        • 2015-01-25
        • 2015-06-13
        • 2013-10-03
        • 2019-02-27
        • 2015-05-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多