process.waitFor () ไม่ส่งคืน


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

โปรดทราบว่าใน JAVA 8 มีการรอสำหรับการโอเวอร์โหลดซึ่งให้คุณระบุระบุระยะหมดเวลา นี่อาจเป็นทางเลือกที่ดีกว่าในการละเว้นจากกรณีที่ waitFor never return
Ikaso

คำตอบ:


146

มีหลายเหตุผลที่waitFor()ไม่กลับมา

แต่โดยปกติแล้วคำสั่งที่ดำเนินการจะไม่หยุดทำงาน

อีกครั้งอาจมีหลายสาเหตุ

สาเหตุทั่วไปประการหนึ่งคือกระบวนการสร้างผลลัพธ์บางอย่างและคุณไม่ได้อ่านจากสตรีมที่เหมาะสม ซึ่งหมายความว่ากระบวนการจะถูกบล็อกทันทีที่บัฟเฟอร์เต็มและรอให้กระบวนการของคุณอ่านต่อ กระบวนการของคุณจะรอให้กระบวนการอื่นเสร็จสิ้น (ซึ่งจะไม่เป็นเช่นนั้นเพราะรอให้กระบวนการของคุณ ... ) นี่คือสถานการณ์การหยุดชะงักแบบคลาสสิก

คุณต้องอ่านจากสตรีมอินพุตของกระบวนการอย่างต่อเนื่องเพื่อให้แน่ใจว่าจะไม่ปิดกั้น

มีบทความดีๆที่อธิบายข้อผิดพลาดทั้งหมดRuntime.exec()และแสดงวิธีการรอบ ๆ ที่เรียกว่า"เมื่อ Runtime.exec () จะไม่" (ใช่บทความนี้มาจากปี 2000 แต่เนื้อหายังคงใช้ได้!)


8
คำตอบนี้ถูกต้อง แต่ไม่มีตัวอย่างโค้ดในการแก้ไขปัญหา ดูคำตอบของ Peter Lawrey สำหรับรหัสที่มีประโยชน์เพื่อหาสาเหตุที่waitFor()ไม่ส่งคืน
ForguesR

84

ดูเหมือนว่าคุณไม่ได้อ่านผลลัพธ์ก่อนที่จะรอให้เสร็จสิ้น จะใช้ได้เฉพาะในกรณีที่เอาต์พุตไม่เติมบัฟเฟอร์ หากเป็นเช่นนั้นระบบจะรอจนกว่าคุณจะอ่านเอาต์พุต catch-22

บางทีคุณอาจมีข้อผิดพลาดบางอย่างที่คุณไม่ได้อ่าน ในกรณีนี้แอปพลิเคชันจะหยุดและรอเพื่อรอตลอดไป วิธีง่ายๆในการแก้ไขปัญหานี้คือกำหนดทิศทางข้อผิดพลาดไปยังเอาต์พุตปกติอีกครั้ง

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);
Jean-François Savard

3
ฉันอยากใช้pb.redirectError(new File("/dev/null"));
Toochka

@Toochka สำหรับข้อมูลredirectErrorมีให้เฉพาะตั้งแต่ Java 1.7
ZhekaKozlov

3
ควรเป็นคำตอบที่ยอมรับฉันเชื่อฉันแทนที่รหัสของฉันด้วยสิ่งนี้และใช้งานได้ทันที
Gerben Rampaart

43

จาก Java doc ด้วย:

java.lang

กระบวนการชั้นเรียน

เนื่องจากแพลตฟอร์มเนทีฟบางแพลตฟอร์มให้ขนาดบัฟเฟอร์ที่ จำกัด สำหรับสตรีมอินพุตและเอาต์พุตมาตรฐานความล้มเหลวในการเขียนสตรีมอินพุตทันทีหรืออ่านสตรีมเอาต์พุตของกระบวนการย่อยอาจทำให้กระบวนการย่อยบล็อกและถึงขั้นหยุดชะงัก

การไม่ล้างบัฟเฟอร์ของอินพุตสตรีม (ซึ่งไปยังสตรีมเอาท์พุตของกระบวนการย่อย) จากกระบวนการอาจนำไปสู่การบล็อกกระบวนการย่อย

ลองสิ่งนี้:

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

17
สองคำเตือน: (1) ใช้ ProcessBuilder + redirectErrorStream (จริง) แล้วคุณจะปลอดภัย อื่น ๆ (2) คุณต้องมีหนึ่งเธรดเพื่ออ่านจาก Process.getInputStream () และอีกเธรดหนึ่งเพื่ออ่านจาก Process.getErrorStream () ใช้เวลาประมาณสี่ชั่วโมงในการค้นหาสิ่งนี้ (!) หรือที่เรียกว่า "The Hard Way"
kevinarpe

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 auto closedable
shaILU

11

ฉันต้องการเพิ่มบางอย่างในคำตอบก่อนหน้านี้ แต่เนื่องจากฉันไม่มีตัวแทนที่จะแสดงความคิดเห็นฉันจึงจะเพิ่มคำตอบ สิ่งนี้มุ่งเน้นไปที่ผู้ใช้ Android ที่กำลังเขียนโปรแกรมใน Java

ตามโพสต์จาก 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
เมื่อคุณไม่ได้มีตัวแทนในการแสดงความคิดเห็นไม่ได้ทำงานอยู่รอบ ๆ และแสดงความคิดเห็นอยู่แล้ว ให้คำตอบนี้ด้วยตัวเองและรับตัวแทนจากมัน!
คดีของ Fund Monica

ฉันไม่คิดว่ามันจะprocess.waitFor()แฮงค์มันreader.readLine()แฮงค์ถ้าคุณไม่มีเอาท์พุท ฉันพยายามใช้การwaitFor(long,TimeUnit)หมดเวลาหากมีสิ่งผิดปกติเกิดขึ้นและพบว่ามันเป็นการอ่านที่หยุดชะงัก ซึ่งทำให้เวอร์ชันหมดเวลาต้องใช้เธรดอื่นในการอ่าน ...
osundblad

5

มีความเป็นไปได้หลายประการ:

  1. stdoutคุณยังไม่ได้บริโภคผลผลิตทั้งหมดที่อยู่ในกระบวนการของ
  2. stderrคุณยังไม่ได้บริโภคผลผลิตทั้งหมดที่อยู่ในกระบวนการของ
  3. กระบวนการที่กำลังรอสำหรับการป้อนข้อมูลstdinจากคุณและคุณไม่ได้ให้มันหรือคุณยังไม่ได้ปิดของกระบวนการ
  4. กระบวนการนี้กำลังหมุนเป็นวงอย่างหนัก

5

เป็นคนอื่นได้กล่าวถึงคุณต้องกินstderrและstdout

เมื่อเทียบกับคำตอบอื่น ๆ เนื่องจาก Java 1.7 นั้นง่ายกว่า คุณไม่จำเป็นต้องสร้างกระทู้ตัวเองอีกต่อไปที่จะอ่านstderrและstdout

เพียงแค่ใช้ProcessBuilderและใช้วิธีการredirectOutputในการทำงานร่วมกับทั้งหรือredirectErrorredirectErrorStream

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%

ขั้นตอนสุดท้ายกำลังเรียกใช้ไฟล์แบตช์นี้โดยใช้ Runtime


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

}


0

การอ่านสตรีมแบบอะซิงโครนัสร่วมกับการหลีกเลี่ยงการรอด้วยการหมดเวลาจะช่วยแก้ปัญหาได้

คุณสามารถค้นหาหน้าอธิบายสิ่งนี้ได้ที่นี่http://simplebasics.net/.net/process-waitforexit-with-a-timeout-will-not-be-able-to-collect-the-output-message/

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.