views:

956

answers:

2

I am really unfamiliar with working with threads, so I was hoping someone could help me figure out the best way to do this.

I have a JButton in my java application...when you click on the button, I have a Process Builder that creates a process which executes some external python code. The python code generates some files, and this can take some time. When the python code is done executing, I need to load those files into an applet within my Java application.

In its current form, I have a p.waitFor() within the code that calls the external python file...so when you click on the button, the button hangs (the entire application hangs actually) until the process is done. Obviously, I want the user to be able to interact with the rest of the application while this process is going on, but as soon as it's done, I want my application to know about it, so that it can load the files into the applet.

What is the best way to do this?

Thanks for your help.

+6  A: 

You should use SwingWorker to invoke the Python process on a background thread. This way your UI will remain responsive whilst the long-running task runs.

// Define Action.
Action action = new AbstractAction("Do It") {
  public void actionPerformed(ActionEvent e) {
    runBackgroundTask();
  }
}

// Install Action into JButton.
JButton btn = new JButton(action);

private void runBackgroundTask() {
  new SwingWorker<Void, Void>() {
    {
      // Disable action until task is complete to prevent concurrent tasks.
      action.setEnabled(false);
    }

    // Called on the Swing thread when background task completes.
    protected void done() {
      action.setEnabled(true);

      try {
        // No result but calling get() will propagate any exceptions onto Swing thread.
        get();
      } catch(Exception ex) {
        // Handle exception
      }
    }

    // Called on background thread
    protected Void doInBackground() throws Exception {
      // Add ProcessBuilder code here!
      return null; // No result so simply return null.
    }
  }.execute();
}
Adamski
Thanks so much. I didn't even know SwingWorker existed...but it worked perfectly.
knt
+1  A: 

You really want to create a new thread for monitoring your new process. As you've discovered, using just one thread for both the UI and monitoring the child process will make the UI seem to hang while the child process runs.

Here's some example code that assumes the existence of a log4j logger which I think will illustrate one possible approach...

Runtime runtime = Runtime.getRuntime();
String[] command = { "myShellCommand", "firstArgument" };

try {

    boolean done = false;
    int exitValue = 0;
    Process proc = runtime.exec(command);

    while (!done) {
        try {
            exitValue = proc.exitValue();
            done = true;
        } catch (IllegalThreadStateException e) {
            // This exception will be thrown only if the process is still running 
            // because exitValue() will not be a valid method call yet...
            logger.info("Process is still running...")
        }
    }

    if (exitValue != 0) {
        // Child process exited with non-zero exit code - do something about failure.
        logger.info("Deletion failure - exit code " + exitValue);
    }

} catch (IOException e) {
    // An exception thrown by runtime.exec() which would mean myShellCommand was not 
    // found in the path or something like that...
    logger.info("Deletion failure - error: " + e.getMessage());
}

// If no errors were caught above, the child is now finished with a zero exit code
// Move on happily
jharlap
Didn't think much about the JButton implying Swing - this is admittedly a somewhat more generic approach which would work equally with SWT or console apps...
jharlap