从Java运行外部程序,读取输出,允许中断

时间:2009-03-18 19:38:22

标签: java multithreading

我想从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中做到这一点的例子吗?

5 个答案:

答案 0 :(得分:11)

以下是我认为你想做的一个例子:

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();
    }
}

你可以改进它:-)我现在没有真正的环境来测试它,但你可以找到更多信息here

答案 1 :(得分:8)

我建议您查看Apache Commons Exec以避免重新创建方向盘。它具有一些很好的功能,如在同步和异步执行之间进行选择,以及产生监视程序进程的标准解决方案,可以帮助超时执行,以防它被卡住。

答案 2 :(得分:5)

像这样的助手类可以解决这个问题:

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);
}

根据您希望破坏进程的条件,您可能希望在单独的线程中执行此操作。否则,您可能会在等待处理更多程序输出时阻塞,并且永远不会真正获得销毁它的选项。

编辑:McDowell在下面的评论中是100%正确的,所以我已经完成了变量。

答案 3 :(得分:2)

这个怎么样(看看它在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();
}

PS。现在,此实现可从com.jcabi.log.VerboseProcess工件jcabi-log类获得。

答案 4 :(得分:0)

什么会让您决定终止进程 - 异步事件(例如来自用户的输入)或同步事件(例如,进程已完成您希望它执行的操作)?我猜它是前者 - 来自用户的输入让你决定取消子进程。

另外,您期望子流程产生多少输出?如果它很多,那么如果你没有足够快地读取它的输出流,那么子进程可能会阻塞。

您的情况可能会有所不同,但似乎您可能需要至少两个不同的线程 - 一个决定是否取消该流程,另一个处理子流程的输出。

请点击此处了解更多详情:http://java.sun.com/developer/JDCTechTips/2005/tt0727.html#2