Exécuter un programme externe à partir de Java, lire la sortie, autoriser une interruption

Je souhaite lancer un processus à partir de Java, lire sa sortie et obtenir son code de retour. Mais pendant son exécution, je veux pouvoir l’annuler. Je commence par lancer le processus:

ProcessBuilder pb = new ProcessBuilder(args); pb.redirectErrorStream(true); Process proc = pb.start(); 

Si j’appelle proc.waitFor (), je ne peux rien faire jusqu’à ce que le processus se termine. Donc, je suppose que j’ai besoin de quelque chose comme ça:

 while (true) { see if process has exited capture some output from the process decide if I want to cancel it, and if so, cancel it sleep for a while } 

Est-ce correct? Quelqu’un peut-il me donner un exemple de la façon de procéder en Java?

Voici un exemple de ce que je pense que vous voulez faire:

 ProcessBuilder pb = new ProcessBuilder(args); pb.redirectErrorStream(true); Process proc = pb.start(); InputStream is = proc.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); Ssortingng line; int exit = -1; while ((line = br.readLine()) != null) { // Outputs your process execution System.out.println(line); try { exit = proc.exitValue(); if (exit == 0) { // Process finished } } catch (IllegalThreadStateException t) { // The process has not yet finished. // Should we stop it? if (processMustStop()) // processMustStop can return true // after time out, for example. proc.destroy(); } } 

Vous pouvez l’améliorer 🙂 Je n’ai pas de véritable environnement pour le tester maintenant, mais vous pouvez trouver plus d’informations ici .

Je recommande de vérifier Apache Commons Exec pour éviter de recréer la roue. Il comporte quelques fonctionnalités intéressantes, telles que le choix entre une exécution synchrone ou asynchrone, ainsi qu’une solution standard pour générer un processus de surveillance qui peut aider à arrêter l’exécution au cas où elle restrait bloquée.

Une classe d’assistance comme celle-ci ferait l’affaire:

 public class ProcessWatcher implements Runnable { private Process p; private volatile boolean finished = false; public ProcessWatcher(Process p) { this.p = p; new Thread(this).start(); } public boolean isFinished() { return finished; } public void run() { try { p.waitFor(); } catch (Exception e) {} finished = true; } } 

Vous devez alors implémenter votre boucle exactement comme vous le décrivez:

 Process p = Runtime.getRuntime().exec("whatever command"); ProcessWatcher pw = new ProcessWatcher(p); InputStream output = p.getInputStream(); while(!pw.isFinished()) { processOutput(output); if(shouldCancel()) p.destroy(); Thread.sleep(500); } 

En fonction des conditions qui vous incitent à détruire le processus, vous pouvez le faire dans un thread séparé. Sinon, vous pouvez bloquer en attendant que plus de programmes soient traités et ne jamais avoir l’option de le détruire.

EDIT : McDowell a 100% raison dans son commentaire ci-dessous, j’ai donc rendu la variable finale volatile.

Que diriez-vous de cela (voir comment cela fonctionne dans jcabi-heroku-maven-plugin ):

 /** * Wait for the process to stop, logging its output in parallel. * @param process The process to wait for * @return Stdout produced by the process * @throws InterruptedException If interrupted in between */ private Ssortingng waitFor(final Process process) throws InterruptedException { final BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()) ); final CountDownLatch done = new CountDownLatch(1); final SsortingngBuffer stdout = new SsortingngBuffer(); new Thread( new VerboseRunnable( new Callable() { @Override public Void call() throws Exception { while (true) { final Ssortingng line = reader.readLine(); if (line == null) { break; } System.out.println(">> " + line); stdout.append(line); } done.countDown(); return null; } }, false ) ).start(); try { process.waitFor(); } finally { done.await(); IOUtils.closeQuietly(reader); } return stdout.toSsortingng(); } 

ps. Cette implémentation est maintenant disponible en tant com.jcabi.log.VerboseProcess classe com.jcabi.log.VerboseProcess à partir de l’artefact jcabi-log .

Qu’est-ce qui vous ferait décider de tuer le processus – un événement asynchrone (comme une entrée de l’utilisateur) ou un événement synchrone (par exemple, le processus a fait ce que vous vouliez qu’il fasse)? J’imagine que c’est le premier cas – l’entrée de l’utilisateur vous incite à annuler le sous-processus.

En outre, quelle quantité de sortie attend le sous-processus? Si c’est beaucoup, le sous-processus peut se bloquer si vous ne lisez pas assez rapidement son stream de sortie.

Votre situation peut varier, mais il semble que vous aurez probablement besoin d’au moins deux threads différents: l’un pour décider si le processus doit être annulé et l’autre qui gère la sortie du sous-processus.

Regardez ici pour un peu plus de détails: http://java.sun.com/developer/JDCTechTips/2005/tt0727.html#2