process.waitFor()永不返回


95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();

请注意,在JAVA 8上有一个waitFor重载,让您指定指定超时。在避免waitFor永不返回的情况下,这可能是一个更好的选择。
Ikaso

Answers:


145

有很多原因waitFor()不回来。

但这通常归结为以下事实:执行的命令不会退出。

同样,这可能有很多原因。

一个普遍的原因是该过程产生了一些输出,而您没有从适当的流中读取。这意味着一旦缓冲区已满,进程就会被阻塞,并等待您的进程继续读取。您的进程依次等待其他进程完成(之所以不会,因为它等待您的进程,...)。这是一个典型的僵局情况。

您需要不断从流程输入流中读取内容,以确保它不会被阻塞。

有一篇很好的文章解释了所有的陷阱,Runtime.exec()并介绍了解决这些陷阱的方法,即“ Runtime.exec()不会。”(是的,该文章来自2000年,但内容仍然适用!)


7
此答案是正确的,但是它缺少用于解决问题的代码示例。看看Peter Lawrey给出的有用代码答案,以找出为什么waitFor()不返回。
ForguesR 2015年

83

似乎您没有在等待输出完成之前读取输出。仅当输出未填满缓冲区时,这才可以。如果是这样,它将等到您读取输出catch 22为止。

也许您有一些未读的错误。这将使应用程序停止,并且waitFor永远等待。解决此问题的一种简单方法是将错误重定向到常规输出。

ProcessBuilder pb = new ProcessBuilder("tasklist");
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null)
    System.out.println("tasklist: " + line);
process.waitFor();

4
有关信息:ProcessBuilder是真正的构建器,可以直接编写ProcessBuilder pb = new ProcessBuilder(“ tasklist”)。redirectErrorStream(true);。
让弗朗索瓦Savard

3
我宁愿使用pb.redirectError(new File("/dev/null"));
Toochka

@Toochka仅供参考,redirectError仅自Java 1.7起
-ZhekaKozlov

3
我认为应该是可以接受的答案,我用这个替换了我的代码,它立即起作用。
Gerben Rampaart '17

43

同样来自Java doc:

java.lang

上课过程

由于某些本机平台仅为标准输入和输出流提供了有限的缓冲区大小,因此未能及时写入子进程的输入流或读取子进程的输出流可能导致子进程阻塞,甚至死锁。

无法从Process中清除输入流(通过管道传输到子流程的输出流)的缓冲区可能导致子流程阻塞。

试试这个:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();

17
两个警告:(1)使用ProcessBuilder + redirectErrorStream(true),那么您很安全。否则,(2)您需要一个线程从Process.getInputStream()读取,而另一个线程从Process.getErrorStream()读取。仅仅花了大约四个小时就解决了这个问题!
kevinarpe11年

1
您可以使用Apache Commons Exec功能同时使用stdout和stderr流:DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);,其中stoutOS和stderrOS是BufferedOutputStream我创建的,可以写出到适当的文件中。
Matthew Wise

就我而言,我从Spring调用了一个批处理文件,该文件在内部打开了一个编辑器。即使应用的代码,我的代码也被挂起process.close()。但是,当我按照上述建议打开输入流并立即关闭时,问题就消失了。因此,在我的情况下,Spring正在等待流关闭信号。即使我正在使用Java 8自动关闭。
shaILU

11

我想在以前的答案中添加一些内容,但是由于我没有代表要评论,因此我只添加一个答案。这是针对使用Java进行编程的android用户。

根据RollingBoy的帖子,此代码几乎对我有用:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();

在我的情况下,waitFor()没有释放,因为我正在执行没有返回值的语句(“ ip adddr flush eth0”)。解决此问题的一种简单方法是仅确保始终在语句中返回某些内容。对我来说,这意味着执行以下命令:“ ip adddr flush eth0 && echo done”。您可以整天读取缓冲区,但是如果没有返回任何内容,则线程将永远不会释放其等待。

希望能帮助到某人!


2
当您没有代表发表评论时,请不要解决它也不要评论。自行回答,并获得代表!
基金莫妮卡的诉讼

我不认为这是process.waitFor()挂起的reader.readLine(),如果没有输出,那就是挂起的。waitFor(long,TimeUnit)如果出现问题,我尝试使用to超时,并发现这是读取挂起的内容。这使得超时的版本需要另一个线程来进行读取...
osundblad

5

有几种可能性:

  1. 您尚未消耗该进程的所有输出stdout
  2. 您尚未消耗该进程的所有输出stderr
  3. 该流程正在等待您的输入,您尚未提供它,或者您尚未关闭该流程的stdin
  4. 这个过程在一个硬循环中旋转。

5

正如其他人提到的那样,您必须使用stderrstdout

与其他答案相比,从Java 1.7开始,它甚至更加容易。您不必再自己创建线程来读取stderrstdout

只需将ProcessBuilderredirectOutput结合使用方法和redirectError或即可redirectErrorStream

String directory = "/working/dir";
File out = new File(...); // File to write stdout to
File err = new File(...); // File to write stderr to
ProcessBuilder builder = new ProcessBuilder();
builder.directory(new File(directory));
builder.command(command);
builder.redirectOutput(out); // Redirect stdout to file
if(out == err) { 
  builder.redirectErrorStream(true); // Combine stderr into stdout
} else { 
  builder.redirectError(err); // Redirect stderr to file
}
Process process = builder.start();

2

出于同样的原因,您还inheritIO()可以将Java控制台与外部应用程序控制台进行映射,例如:

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

pb.directory(new File(appFile.getParent()));
pb.inheritIO();

Process process = pb.start();
int success = process.waitFor();

2

您应该尝试同时消耗输出和错误

    private void runCMD(String CMD) throws IOException, InterruptedException {
    System.out.println("Standard output: " + CMD);
    Process process = Runtime.getRuntime().exec(CMD);

    // Get input streams
    BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    String line = "";
    String newLineCharacter = System.getProperty("line.separator");

    boolean isOutReady = false;
    boolean isErrorReady = false;
    boolean isProcessAlive = false;

    boolean isErrorOut = true;
    boolean isErrorError = true;


    System.out.println("Read command ");
    while (process.isAlive()) {
        //Read the stdOut

        do {
            isOutReady = stdInput.ready();
            //System.out.println("OUT READY " + isOutReady);
            isErrorOut = true;
            isErrorError = true;

            if (isOutReady) {
                line = stdInput.readLine();
                isErrorOut = false;
                System.out.println("=====================================================================================" + line + newLineCharacter);
            }
            isErrorReady = stdError.ready();
            //System.out.println("ERROR READY " + isErrorReady);
            if (isErrorReady) {
                line = stdError.readLine();
                isErrorError = false;
                System.out.println("ERROR::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::" + line + newLineCharacter);

            }
            isProcessAlive = process.isAlive();
            //System.out.println("Process Alive " + isProcessAlive);
            if (!isProcessAlive) {
                System.out.println(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Process DIE " + line + newLineCharacter);
                line = null;
                isErrorError = false;
                process.waitFor(1000, TimeUnit.MILLISECONDS);
            }

        } while (line != null);

        //Nothing else to read, lets pause for a bit before trying again
        System.out.println("PROCESS WAIT FOR");
        process.waitFor(100, TimeUnit.MILLISECONDS);
    }
    System.out.println("Command finished");
}

1

我认为我观察到了一个类似的问题:某些进程已启动,似乎运行成功,但从未完成。函数waitFor()一直在等待,除非我在任务管理器中终止了该进程。
但是,如果命令行的长度为127个字符或更短,则一切正常。如果不可避免要使用长文件名,则可能要使用环境变量,这可以使命令行字符串保持简短。您可以生成一个批处理文件(使用FileWriter),在其中调用实际要运行的程序之前,在其中设置环境变量。这样一批的内容可能看起来像:

    set INPUTFILE="C:\Directory 0\Subdirectory 1\AnyFileName"
    set OUTPUTFILE="C:\Directory 2\Subdirectory 3\AnotherFileName"
    set MYPROG="C:\Directory 4\Subdirectory 5\ExecutableFileName.exe"
    %MYPROG% %INPUTFILE% %OUTPUTFILE%

最后一步是使用运行时运行此批处理文件。


1

这是一种对我有用的方法。注意:此方法中有一些代码可能不适用于您,因此请尝试忽略它。例如“ logStandardOut(...),git-bash等”。

private String exeShellCommand(String doCommand, String inDir, boolean ignoreErrors) {
logStandardOut("> %s", doCommand);

ProcessBuilder builder = new ProcessBuilder();
StringBuilder stdOut = new StringBuilder();
StringBuilder stdErr = new StringBuilder();

boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
if (isWindows) {
  String gitBashPathForWindows = "C:\\Program Files\\Git\\bin\\bash";
  builder.command(gitBashPathForWindows, "-c", doCommand);
} else {
  builder.command("bash", "-c", doCommand);
}

//Do we need to change dirs?
if (inDir != null) {
  builder.directory(new File(inDir));
}

//Execute it
Process process = null;
BufferedReader brStdOut;
BufferedReader brStdErr;
try {
  //Start the command line process
  process = builder.start();

  //This hangs on a large file
  // /programming/5483830/process-waitfor-never-returns
  //exitCode = process.waitFor();

  //This will have both StdIn and StdErr
  brStdOut = new BufferedReader(new InputStreamReader(process.getInputStream()));
  brStdErr = new BufferedReader(new InputStreamReader(process.getErrorStream()));

  //Get the process output
  String line = null;
  String newLineCharacter = System.getProperty("line.separator");

  while (process.isAlive()) {
    //Read the stdOut
    while ((line = brStdOut.readLine()) != null) {
      stdOut.append(line + newLineCharacter);
    }

    //Read the stdErr
    while ((line = brStdErr.readLine()) != null) {
      stdErr.append(line + newLineCharacter);
    }

    //Nothing else to read, lets pause for a bit before trying again
    process.waitFor(100, TimeUnit.MILLISECONDS);
  }

  //Read anything left, after the process exited
  while ((line = brStdOut.readLine()) != null) {
    stdOut.append(line + newLineCharacter);
  }

  //Read anything left, after the process exited
  while ((line = brStdErr.readLine()) != null) {
    stdErr.append(line + newLineCharacter);
  }

  //cleanup
  if (brStdOut != null) {
    brStdOut.close();
  }

  if (brStdErr != null) {
    brStdOut.close();
  }

  //Log non-zero exit values
  if (!ignoreErrors && process.exitValue() != 0) {
    String exMsg = String.format("%s%nprocess.exitValue=%s", stdErr, process.exitValue());
    throw new ExecuteCommandException(exMsg);
  }

} catch (ExecuteCommandException e) {
  throw e;
} catch (Exception e) {
  throw new ExecuteCommandException(stdErr.toString(), e);
} finally {
  //Log the results
  logStandardOut(stdOut.toString());
  logStandardError(stdErr.toString());
}

return stdOut.toString();

}


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.