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).