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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/659796/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:30:43  来源:igfitidea点击:

Run external program from Java, read output, allow interruption

javamultithreading

提问by JW.

I want to launch a process from Java, read its output, and get its return code. But while it's executing, I want to be able to cancel it. I start out by launching the process:

我想从 Java 启动一个进程,读取其输出并获取其返回码。但是在它执行时,我希望能够取消它。我首先启动这个过程:

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

If I call proc.waitFor(), I can't do anything until the process exits. So I'm assuming I need to something like this:

如果我调用 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
}

Is this right? Can someone give me an example of how to do this in Java?

这是正确的吗?有人可以给我一个如何在 Java 中执行此操作的示例吗?

回答by Paulo Guedes

Here's an example of what I think you want to do:

这是我认为你想要做的一个例子:

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

You can improve it :-) I don't have a real environment to test it now, but you can find some more information here.

你可以改进它 :-) 我现在没有一个真实的环境来测试它,但你可以在这里找到更多信息。

回答by Marty Lamb

A helper class like this would do the trick:

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

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

}

You would then implement your loop exactly as you describe:

然后,您将完全按照您的描述实现您的循环:

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

Depending upon what conditions would make you want to destroy the process, you might want to do that in a separate thread. Otherwise, you may block while waiting for more program output to process and never really get the option to destroy it.

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

EDIT: McDowell is 100% right in his comment below, so I've made the finished variable volatile.

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

回答by Dan Breslau

What would make you decide to kill the process -- an asynchronous event (such as input from the user), or a synchronous event (e.g., the process has done what you wanted it to do)? I'm guessing it's the former -- input from the user makes you decide to cancel the subprocess.

是什么让您决定终止进程——异步事件(例如来自用户的输入),还是同步事件(例如,进程完成了您希望它做的事情)?我猜是前者——来自用户的输入让你决定取消子流程。

Also, how much output do you expect the subprocess to produce? If it's a lot, then the subprocess may block if you don't read from its output stream quickly enough.

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

Your situation may vary, but it seems that you're likely going to need at least two different threads -- one to decide whether to cancel the process, and one that handles the output from the subprocess.

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

Have a look here for a bit more detail: http://java.sun.com/developer/JDCTechTips/2005/tt0727.html#2

在这里查看更多细节:http: //java.sun.com/developer/JDCTechTips/2005/tt0727.html#2

回答by AWhitford

I recommend checking out Apache Commons Execto avoid recreating the wheel. It has some nice features like choosing between synchronous vs. asynchronous execution, as well as a standard solution to spawning a watchdogprocess that can help in timing out the execution in case it gets stuck.

我建议查看Apache Commons Exec以避免重新创建轮子。它有一些不错的功能,比如在同步执行和异步执行之间进行选择,以及生成看门狗进程的标准解决方案,可以帮助在执行超时的情况下卡住。

回答by yegor256

How about this (see how it works in jcabi-heroku-maven-plugin):

这个怎么样(看看它是如何在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. Now this implementation is available as com.jcabi.log.VerboseProcessclass from jcabi-logartifact.

附:现在这个实现可以作为com.jcabi.log.VerboseProcess来自jcabi-log工件的类。