Home > Mobile >  How can I make a thread in JavaFX?
How can I make a thread in JavaFX?

Time:07-24

I want to parse url after I input something in text dialog and comfirm.
But it will cost time, so I created a thread to do it:

public void onAddMod(ActionEvent actionEvent) throws IOException {
    Platform.runLater(() -> {
        status.setText("Downloading...");
        TextInputDialog dialog = new TextInputDialog("fabric-api");
        dialog.setTitle("Add a mod from Modrinth");
        dialog.setHeaderText("");
        dialog.setContentText("Modrinth slug: ");
        Optional<String> opt = dialog.showAndWait();
        if (opt.isPresent()) {
            try {
                // parsing
                Information info = ModrinthUtils.information(opt.get());
                DataSaver.MODS.put(info.modName(), info);
                ModList.getItems().add(info.modName());
            } catch (IOException ignored) {}
        }
        status.setText("Finished!");
    });
}

*onAddMod() will run after the button pressed

However, Platform.runLater() CANNOT let the thread "run later". When I confirm, the program stopped until parsing finished.

How can I fix it? Where am I doing wrong?

CodePudding user response:

Here's an excerpt from the documentation of Platform#runLater(Runnable):

Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted.

But your onAddMod method is already being invoked by the JavaFX Application Thread. So, all your current code is doing is delaying the code's execution by a small amount.

You need to modify your code to use a background thread. Creating a new thread is done the same way as in any other Java application. If you want just a one-off thread, then create a new Thread instance, passing it a Runnable, and call Thread#start(). If you want a thread pool, then make use of the Executor / ExecutorService API (also see the Executors class).

But as this is a JavaFX application, you probably also want to make use of the classes in the javafx.concurrent package. See Concurrency in JavaFX. For example:

public void onAddMod(ActionEvent actionEvent) {
  // run dialog code on FX thread
  TextInputDialog dialog = new TextInputDialog("fabric-api");
  dialog.setTitle("Add a mod from Modrinth");
  dialog.setHeaderText("");
  dialog.setContentText("Modrinth slug: ");
  Optional<String> opt = dialog.showAndWait();

  if (opt.isPresent()) {
    Task<Information> task = new Task<>() {

      @Override
      protected Information call() throws Exception {
        // this method is invoked on the background thread

        updateMessage("Downloading..."); // update coalesced on FX thread
        Information info = ModrinthUtils.information(opt.get());
        updateMessage("Finished!"); // update coalesced on FX thread
        return info;
      }
    };

    // might want to unbind when the task finishes?
    status.textProperty().bind(task.messageProperty());

    task.setOnSucceeded(event -> {
      // this event handler is invoked on the FX thread

      Information info = task.getValue();
      DataSaver.MODS.put(info.modName(), info);
      ModList.getItems().add(info.modName());
    });

    // this event handler is also invoked on the FX thread
    task.setOnFailed(event -> /* notify user of failure */ );

    Thread thread = new Thread(task); // 'Task' is a 'Runnable'
    thread.setDaemon(true);
    thread.start(); // start the background thread
  }
}

The above makes use of Task and creates a one-off Thread instance. See Service for a reusable JavaFX worker, which also makes use of Executor so that you reuse the same thread pool.

I didn't know if:

DataSaver.MODS.put(info.modName(), info);
ModList.getItems().add(info.modName());

Needed to be executed on a certain thread, so I just put it on the FX thread, same as your current code, via the onSucceeded handler (the example puts the parsing on the background thread, if I understand your code correctly).

  • Related