Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running a process in a separate thread so rest of Java FX application is usable?

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?

like image 427
Josh Avatar asked Mar 08 '23 22:03

Josh


1 Answers

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.

like image 54
James_D Avatar answered Mar 12 '23 00:03

James_D