process.waitFor () tidak pernah kembali

95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
pengguna590444
sumber
Harap dicatat bahwa di JAVA 8 ada waitFor overload yang memungkinkan Anda menentukan batas waktu. Ini mungkin pilihan yang lebih baik untuk menahan diri dari kasus di mana waitFor tidak pernah kembali.
Ikaso

Jawaban:

145

Ada banyak alasan yang waitFor()tidak kembali.

Tetapi biasanya intinya adalah fakta bahwa perintah yang dieksekusi tidak berhenti.

Ini, sekali lagi, dapat memiliki banyak alasan.

Salah satu alasan yang umum adalah bahwa proses tersebut menghasilkan beberapa keluaran dan Anda tidak membaca dari aliran yang sesuai. Ini berarti proses diblokir segera setelah buffer penuh dan menunggu proses Anda untuk melanjutkan membaca. Proses Anda pada gilirannya menunggu proses lainnya selesai (yang tidak akan terjadi karena menunggu proses Anda, ...). Ini adalah situasi kebuntuan klasik.

Anda perlu terus membaca dari aliran input proses untuk memastikan bahwa itu tidak memblokir.

Ada artikel bagus yang menjelaskan semua jebakan Runtime.exec()dan menunjukkan cara di sekitarnya yang disebut "When Runtime.exec () tidak mau" (ya, artikel ini dari tahun 2000, tetapi kontennya masih berlaku!)

Joachim Sauer
sumber
7
Jawaban ini benar tetapi melewatkan contoh kode untuk memecahkan masalah. Lihat jawaban Peter Lawrey untuk kode yang berguna untuk mengetahui mengapa waitFor()tidak kembali.
MelupakanR
83

Tampaknya Anda tidak membaca output sebelum menunggu hingga selesai. Ini bagus hanya jika output tidak memenuhi buffer. Jika ya, itu akan menunggu sampai Anda membaca output, catch-22.

Mungkin Anda memiliki beberapa kesalahan yang tidak Anda baca. Ini akan membuat aplikasi berhenti dan menunggu untuk menunggu selamanya. Cara sederhana untuk menyiasatinya adalah dengan mengarahkan kembali kesalahan ke keluaran biasa.

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();
Peter Lawrey
sumber
4
Untuk info: ProcessBuilder menjadi pembangun nyata, Anda dapat langsung menulis ProcessBuilder pb = new ProcessBuilder ("tasklist"). RedirectErrorStream (true);
Jean-François Savard
3
Saya lebih suka menggunakanpb.redirectError(new File("/dev/null"));
Toochka
@Toochka Hanya untuk informasi, redirectErrorhanya tersedia sejak Java 1.7
ZhekaKozlov
3
Seharusnya jawaban yang diterima saya percaya, saya mengganti kode saya dengan ini dan itu segera berfungsi.
Gerben Rampaart
43

Juga dari dokumen Java:

java.lang

Proses Kelas

Karena beberapa platform asli hanya menyediakan ukuran buffer terbatas untuk aliran input dan output standar, kegagalan untuk segera menulis aliran input atau membaca aliran output dari subproses dapat menyebabkan subproses diblokir, dan bahkan kebuntuan.

Gagal membersihkan buffer aliran input (yang menyalurkan ke aliran output subproses) dari Proses dapat menyebabkan pemblokiran subproses.

Coba ini:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();
RollingBoy
sumber
17
Dua peringatan: (1) Gunakan ProcessBuilder + redirectErrorStream (true), maka Anda aman. Jika tidak, (2) Anda memerlukan satu utas untuk dibaca dari Process.getInputStream () dan utas lainnya untuk membaca dari Process.getErrorStream (). Hanya menghabiskan waktu sekitar empat jam untuk memikirkan ini (!), Alias, "Cara yang Sulit".
kevinarpe
1
Anda dapat menggunakan fungsionalitas Apache Commons Exec untuk menggunakan aliran stdout dan stderr secara bersamaan DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);:, dengan stoutOS dan stderrOS yang BufferedOutputStreamsaya buat untuk menulis ke file yang sesuai.
Matthew Wise
Dalam kasus saya, saya memanggil file batch dari Spring yang secara internal membuka satu editor. Kode saya menggantung bahkan setelah menerapkan kode process.close(). Tetapi ketika saya membuka aliran input seperti yang disarankan di atas dan segera menutup - masalah hilang. Jadi dalam kasus saya Spring sedang menunggu sinyal tutup aliran. Meskipun saya menggunakan Java 8 auto closable.
shaILU
11

Saya ingin menambahkan sesuatu ke jawaban sebelumnya tetapi karena saya tidak memiliki perwakilan untuk berkomentar, saya hanya akan menambahkan jawaban. Ini ditujukan untuk pengguna android yang memprogram di Java.

Sesuai posting dari RollingBoy, kode ini hampir berhasil untuk saya:

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

Dalam kasus saya, waitFor () tidak merilis karena saya menjalankan pernyataan tanpa pengembalian ("ip adddr flush eth0"). Cara mudah untuk memperbaikinya adalah dengan memastikan Anda selalu mengembalikan sesuatu dalam pernyataan Anda. Bagi saya, itu berarti menjalankan yang berikut: "ip adddr flush eth0 && echo done". Anda dapat membaca buffer sepanjang hari, tetapi jika tidak ada yang dikembalikan, thread Anda tidak akan pernah melepaskan penantiannya.

Semoga itu bisa membantu seseorang!

Lereng
sumber
2
Jika Anda tidak memiliki perwakilan untuk berkomentar, jangan mengatasinya dan tetap berkomentar . Jadikan ini jawaban sendiri dan dapatkan reputasi darinya!
Gugatan Dana Monica
Saya tidak berpikir itu adalah process.waitFor()yang hang itu reader.readLine()yang hang jika Anda tidak memiliki output. Saya mencoba menggunakan waitFor(long,TimeUnit)to timeout jika terjadi kesalahan dan menemukan bahwa itu adalah read the hung. Yang membuat versi timeout membutuhkan utas lain untuk melakukan pembacaan ...
osundblad
5

Ada beberapa kemungkinan:

  1. Anda belum menghabiskan semua output dari proses tersebut stdout.
  2. Anda belum menghabiskan semua output dari proses tersebut stderr.
  3. Prosesnya menunggu masukan dari Anda dan Anda belum menyediakannya, atau Anda belum menutup prosesnya stdin.
  4. Proses ini berputar dalam putaran yang keras.
Marquis dari Lorne
sumber
5

Seperti yang disebutkan orang lain, Anda harus mengonsumsi stderr dan stdout .

Dibandingkan dengan jawaban lain, karena Java 1.7 bahkan lebih mudah. Anda tidak perlu lagi membuat utas untuk membaca stderr dan stdout .

Cukup gunakan ProcessBuilderdan gunakan metode yang redirectOutputdikombinasikan dengan salah satu redirectErroratau 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();
Markus Weninger
sumber
2

Untuk alasan yang sama, Anda juga dapat menggunakan inheritIO()untuk memetakan konsol Java dengan konsol aplikasi eksternal seperti:

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

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

Process process = pb.start();
int success = process.waitFor();
Vivek Dhiman
sumber
2

Anda harus mencoba mengkonsumsi output dan error pada saat yang bersamaan

    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");
}
Eduardo Reyes
sumber
1

Saya rasa saya mengamati masalah serupa: beberapa proses dimulai, tampaknya berjalan dengan sukses tetapi tidak pernah selesai. Fungsi waitFor () menunggu selamanya kecuali jika saya mematikan proses di Task Manager.
Namun, semuanya bekerja dengan baik jika panjang baris perintah 127 karakter atau lebih pendek. Jika nama file yang panjang tidak dapat dihindari, Anda mungkin ingin menggunakan variabel lingkungan, yang memungkinkan Anda membuat string baris perintah tetap pendek. Anda dapat membuat file batch (menggunakan FileWriter) di mana Anda menetapkan variabel lingkungan Anda sebelum memanggil program yang sebenarnya ingin Anda jalankan. Isi kumpulan seperti itu akan terlihat seperti:

    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%

Langkah terakhir adalah menjalankan file batch ini menggunakan Runtime.

Kauz_at_Vancouver
sumber
1

Inilah metode yang berhasil untuk saya. CATATAN: Ada beberapa kode dalam metode ini yang mungkin tidak berlaku untuk Anda, jadi coba dan abaikan. Misalnya "logStandardOut (...), git-bash, dll".

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

}

Sagan
sumber