I am currently creating a Java application with a UI created using Java FX. My application's interface is divided into three separate tabs. On the first tab, I have a lot of buttons that launch other applications. My goal is for the user to launch these applications in a certain order, so the succeeding buttons are disabled out until the previous application's process has finished, then they are enabled. I have accomplished this by using the lines
Process p = Runtime.getRuntime().exec(application);
p.waitFor();
Then the controller enables the next button in the interface. My problem arises when the launched application is running. Due to my application waiting for the process to end, task manager shows my Java application as not responding, and you are unable to navigate to the other tabs. Of course when the launched application has finished, my application functions normally.
I was thinking that it's due to the main thread being blocked as it's waiting on a process to finish. Would the best solution be to run this process on a separate thread so the rest of the application is usable while waiting on a launched process to finish?
I was thinking that it's due to the main thread being blocked as it's waiting on a process to finish.
This is correct.
Is it possible to run this process on a seperate thread so the rest of the application is usable while waiting on a launched process to finish?
Yes, of course. The simplest thing to do is just to launch this on a separate thread:
Process p = Runtime.getRuntime().exec(application);
new Thread(p::waitFor).start();
However, you probably need a little more that that: e.g. to be notified when that new thread finishes, etc. JavaFX provides a concurrency API that has callbacks for the lifecycle of the tasks you execute. These callbacks are executed back on the FX Application Thread, so it is safe to update the UI in the callbacks (you cannot update the UI from a background thread).
So you can do something like
Task<Void> executeAppTask = new Task<Void>() {
@Override
protected Void call() throws Exception {
Process p = Runtime.getRuntime().exec(application);
p.waitFor();
return null;
}
};
executeAppTask.setOnSucceeded(e -> {
/* code to execute when task completes normally */
});
executeAppTask.setOnFailed(e -> {
Throwable problem = executeAppTask.getException();
/* code to execute if task throws exception */
});
executeAppTask.setOnCancelled(e -> {
/* task was cancelled */
});
Thread thread = new Thread(executeAppTask);
thread.start();
Note that the setOnSucceeded()
, setOnFailed()
, and setOnCancelled()
handlers are executed on the FX Application Thread, so, again, it is safe to update the UI in those handlers. Also note that calling executeAppTask.cancel()
will interrupt the thread running the task, if necessary, which will properly interrupt the waitFor()
method. Thus you will (more or less) immediately invoke the setOnCancelled
handler by calling executeAppTask.cancel()
. If there is code in the call()
method executed after a call to a blocking method, such as waitFor()
, it should probably catch the interrupted exception and check the isCancelled()
flag, exiting gracefully if the task has been cancelled.
See the Task
API docs for many more examples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With