Запускать внешнюю программу с Java, читать вывод, разрешать прерывание
Я хочу запустить процесс с Java, прочитать его вывод и получить его код возврата. Но пока он выполняется, я хочу иметь возможность отменить его. Я начинаю с запуска процесса:
ProcessBuilder pb = new ProcessBuilder(args);
pb.redirectErrorStream(true);
Process proc = pb.start();
Если я вызываю proc.waitFor(), я ничего не могу сделать, пока процесс не завершится. Поэтому я предполагаю, что мне нужно что-то вроде этого:
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
}
Правильно ли это? Может ли кто-нибудь дать мне пример того, как это сделать на Java?
Ответы
Ответ 1
Вот пример того, что я думаю, что вы хотите сделать:
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);
String 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();
}
}
Вы можете улучшить его:-) У меня нет реальной среды для тестирования, но вы можете найти дополнительную информацию здесь.
Ответ 2
Я рекомендую проверить Apache Commons Exec, чтобы избежать воссоздания колеса. Он имеет несколько приятных функций, таких как выбор между синхронным и асинхронным выполнением, а также стандартное решение для создания механизма сторожевого таймера, который может помочь в отсрочке исполнения, если он застрял.
Ответ 3
Вспомогательный класс, подобный этому, мог бы сделать трюк:
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;
}
}
Затем вы реализуете свой цикл точно так, как вы описываете:
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);
}
В зависимости от того, какие условия заставят вас уничтожить процесс, вы можете сделать это в отдельном потоке. В противном случае вы можете заблокировать, ожидая, когда будет выведено больше выходных данных программы, и никогда не получите возможность ее уничтожить.
EDIT: McDowell на 100% прав в своем комментарии ниже, поэтому я сделал готовую переменную volatile.
Ответ 4
Как насчет этого (см., как он работает в 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 String waitFor(final Process process) throws InterruptedException {
final BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
final CountDownLatch done = new CountDownLatch(1);
final StringBuffer stdout = new StringBuffer();
new Thread(
new VerboseRunnable(
new Callable<Void>() {
@Override
public Void call() throws Exception {
while (true) {
final String 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.toString();
}
пс. Теперь эта реализация доступна как com.jcabi.log.VerboseProcess
класс из jcabi-log артефакт.
Ответ 5
Что бы вы решили убить процесс - асинхронное событие (например, ввод от пользователя) или синхронное событие (например, процесс сделал то, что вы хотели, чтобы он сделал)? Я предполагаю, что это первый - вход от пользователя заставит вас решить отменить подпроцесс.
Кроме того, какой объем вы ожидаете от подпроцесса? Если это много, то подпроцесс может блокироваться, если вы не читаете его поток вывода достаточно быстро.
Ваша ситуация может отличаться, но кажется, что вам, вероятно, понадобится как минимум два разных потока: один, чтобы решить, отменить ли процесс, и тот, который обрабатывает вывод из подпроцесса.
Посмотрите здесь более подробно: http://java.sun.com/developer/JDCTechTips/2005/tt0727.html#2