process.waitFor () không bao giờ trả về


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

Xin lưu ý rằng trên JAVA 8 có quá tải waitFor cho phép bạn chỉ định thời gian chờ. Đây có thể là lựa chọn tốt hơn để tránh trường hợp người chờ đợi không bao giờ trở lại.
Ikaso

Câu trả lời:


145

Có nhiều lý do mà waitFor()nó không quay trở lại.

Nhưng nó thường dẫn đến thực tế là lệnh được thực thi không thoát.

Điều này, một lần nữa, có thể có nhiều lý do.

Một lý do phổ biến là quá trình tạo ra một số đầu ra và bạn không đọc từ các luồng thích hợp. Điều này có nghĩa là quá trình bị chặn ngay sau khi bộ đệm đầy và chờ quá trình của bạn tiếp tục đọc. Đến lượt quá trình của bạn sẽ đợi quá trình khác kết thúc (điều này sẽ không xảy ra vì nó đang đợi quá trình của bạn, ...). Đây là một tình huống bế tắc cổ điển.

Bạn cần liên tục đọc từ luồng đầu vào của quy trình để đảm bảo rằng nó không bị chặn.

Có một bài viết hay giải thích tất cả những cạm bẫy Runtime.exec()và chỉ ra những cách xung quanh chúng có tên "When Runtime.exec () sẽ không" (vâng, bài viết có từ năm 2000, nhưng nội dung vẫn được áp dụng!)


7
Câu trả lời này đúng nhưng nó thiếu một mẫu mã để khắc phục sự cố. Hãy xem câu trả lời của Peter Lawrey để biết mã hữu ích để tìm hiểu lý do tại sao waitFor()không quay lại.
ForguesR

83

Có vẻ như bạn không đọc kết quả trước khi đợi nó kết thúc. Điều này chỉ tốt nếu đầu ra không lấp đầy bộ đệm. Nếu có, nó sẽ đợi cho đến khi bạn đọc đầu ra, bắt-22.

Có lẽ bạn có một số lỗi mà bạn không đọc. Điều này sẽ khiến ứng dụng dừng lại và chờ Một cách đơn giản để giải quyết vấn đề này là chuyển hướng các lỗi đến đầu ra thông thường.

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
Để biết thông tin: ProcessBuilder là một trình xây dựng thực sự, bạn có thể viết trực tiếp ProcessBuilder pb = new ProcessBuilder ("tasklist"). RedirectErrorStream (true);
Jean-François Savard

3
Tôi muốn sử dụngpb.redirectError(new File("/dev/null"));
Toochka

@Toochka Chỉ cần cung cấp thông tin, redirectErrorchỉ có sẵn từ Java 1,7
ZhekaKozlov

3
Nếu tôi tin rằng câu trả lời được chấp nhận, tôi đã thay thế mã của mình bằng mã này và nó ngay lập tức hoạt động.
Gerben Rampaart

43

Cũng từ tài liệu Java:

java.lang

Tiến trình lớp học

Bởi vì một số nền tảng gốc chỉ cung cấp kích thước bộ đệm giới hạn cho các luồng đầu vào và đầu ra tiêu chuẩn, việc không ghi kịp thời luồng đầu vào hoặc đọc luồng đầu ra của quy trình con có thể khiến quy trình con bị chặn và thậm chí là bế tắc.

Việc không xóa bộ đệm của luồng đầu vào (dẫn đến luồng đầu ra của quy trình con) khỏi Quy trình có thể dẫn đến chặn quy trình con.

Thử cái này:

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

17
Hai lưu ý: (1) Sử dụng ProcessBuilder + redirectErrorStream (true), sau đó bạn được an toàn. Ngoài ra, (2) bạn cần một luồng để đọc từ Process.getInputStream () và một luồng khác để đọc từ Process.getErrorStream (). Chỉ dành khoảng bốn giờ để tìm ra điều này (!), Hay còn gọi là "Con đường khó khăn".
kevinarpe 14/09/11

1
Bạn có thể sử dụng chức năng Apache Commons Exec để sử dụng đồng thời các luồng stdout và stderr DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);:, trong đó stoutOS và stderrOS là những thứ BufferedOutputStreammà tôi đã tạo để ghi ra các tệp thích hợp.
Matthew Wise

Trong trường hợp của tôi, tôi đang gọi một tệp hàng loạt từ Spring sẽ mở nội bộ một trình chỉnh sửa. Mã của tôi bị treo ngay cả sau khi áp dụng mã của process.close(). Nhưng khi tôi mở luồng đầu vào như đề xuất ở trên và đóng ngay lập tức - vấn đề sẽ biến mất. Vì vậy, trong trường hợp của tôi, Spring đã chờ đợi tín hiệu đóng luồng. Mặc dù tôi đang sử dụng Java 8 có thể đóng tự động.
shaILU

10

Tôi muốn thêm một cái gì đó vào các câu trả lời trước nhưng vì tôi không có người đại diện để bình luận, tôi sẽ chỉ thêm một câu trả lời. Điều này hướng tới người dùng Android đang lập trình bằng Java.

Theo bài đăng từ RollingBoy, mã này gần như phù hợp với tôi:

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

Trong trường hợp của tôi, waitFor () không được giải phóng vì tôi đang thực hiện một câu lệnh không có trả về ("ip adddr flush eth0"). Một cách dễ dàng để khắc phục điều này là chỉ cần đảm bảo rằng bạn luôn trả lại nội dung nào đó trong bảng sao kê của mình. Đối với tôi, điều đó có nghĩa là thực hiện như sau: "ip adddr flush eth0 && echo done". Bạn có thể đọc bộ đệm cả ngày, nhưng nếu không có gì được trả lại, chuỗi của bạn sẽ không bao giờ giải phóng sự chờ đợi của nó.

Hy vọng rằng sẽ giúp một ai đó!


2
Khi bạn không có đại diện để bình luận, đừng làm việc xung quanh nó và bình luận bằng mọi cách . Hãy tự mình đưa ra câu trả lời và nhận đại diện từ nó!
Vụ kiện của Fund Monica vào

Tôi không nghĩ nó là process.waitFor()cái treo nó là reader.readLine()cái treo nếu bạn không có đầu ra. Tôi đã cố gắng sử dụng waitFor(long,TimeUnit)thời gian chờ nếu có gì đó không ổn và phát hiện ra rằng đó là lỗi đã đọc. Mà làm cho phiên bản timeouted đòi hỏi thread khác để làm việc đọc ...
osundblad

5

Có một số khả năng:

  1. Bạn đã không sử dụng tất cả đầu ra của quy trình stdout.
  2. Bạn đã không sử dụng tất cả đầu ra của quy trình stderr.
  3. Quá trình này đang đợi bạn nhập liệu và bạn chưa cung cấp hoặc bạn chưa đóng quá trình stdin.
  4. Quá trình này đang quay trong một vòng lặp khó khăn.

5

Như những người khác đã đề cập, bạn phải sử dụng stderrstdout .

So với các câu trả lời khác, kể từ Java 1.7, nó thậm chí còn dễ dàng hơn. Bạn không cần phải tự tạo các luồng để đọc stderrstdout .

Chỉ cần sử dụng ProcessBuildervà sử dụng các phương pháp redirectOutputkết hợp với redirectErrorhoặc 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

Vì lý do tương tự, bạn cũng có thể sử dụng inheritIO()để ánh xạ bảng điều khiển Java với bảng điều khiển ứng dụng bên ngoài như:

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

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

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

2

Bạn nên thử tiêu thụ đầu ra và lỗi trong cùng một thời gian

    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

Tôi nghĩ rằng tôi đã quan sát thấy một vấn đề tương tự: một số quy trình bắt đầu, dường như chạy thành công nhưng không bao giờ hoàn thành. Hàm waitFor () đã đợi mãi mãi ngoại trừ trường hợp tôi đã giết quá trình trong Trình quản lý tác vụ.
Tuy nhiên, mọi thứ hoạt động tốt trong trường hợp độ dài của dòng lệnh là 127 ký tự hoặc ngắn hơn. Nếu tên tệp dài là không thể tránh khỏi, bạn có thể muốn sử dụng các biến môi trường, điều này có thể cho phép bạn giữ chuỗi dòng lệnh ngắn. Bạn có thể tạo một tệp hàng loạt (sử dụng FileWriter) trong đó bạn đặt các biến môi trường của mình trước khi gọi chương trình bạn thực sự muốn chạy. Nội dung của một lô như vậy có thể giống như sau:

    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%

Bước cuối cùng là chạy tệp hàng loạt này bằng Runtime.


1

Đây là một phương pháp phù hợp với tôi. LƯU Ý: Có một số mã trong phương pháp này có thể không áp dụng cho bạn, vì vậy hãy thử và bỏ qua nó. Ví dụ: "logStandardOut (...), git-bash, v.v.".

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

}


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.