process.waitFor()は決して戻りません


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

JAVA 8には、タイムアウトを指定するためのwaitForオーバーロードがあることに注意してください。これは、waitForが決して戻らないケースを控えるためのより良い選択かもしれません。
Ikaso

回答:


145

それには多くの理由があります waitFor()戻らない。

しかし、それは通常、実行されたコマンドが終了しないという事実に要約されます。

これも、多くの理由が考えられます。

一般的な理由の1つは、プロセスが出力を生成し、適切なストリームから読み取らないことです。つまり、バッファがいっぱいになるとすぐにプロセスがブロックされ、プロセスが読み取りを続行するのを待ちます。次に、プロセスは他のプロセスが完了するのを待ちます(プロセスを待つので、そうなりません...)。これは古典的なデッドロック状況です。

プロセスの入力ストリームから継続的に読み取り、ブロックされないようにする必要があります。

「Runtime.exec()が実行されない場合」Runtime.exec()と呼ばれるすべての落とし穴を説明し、その回避策を示す素晴らしい記事があります(そうです、記事は2000年のものですが、コンテンツは引き続き適用されます!)


7
この答えは正しいですが、問題をトラブルシューティングするためのコードサンプルがありません。ピーター・ローリーの回答を見て、なぜコードが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
infoの場合: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 2017

43

またJavaドキュメントから:

java.lang

クラスプロセス

一部のネイティブプラットフォームは標準の入力および出力ストリームに限られたバッファーサイズしか提供しないため、サブプロセスの入力ストリームの書き込みまたは出力ストリームの読み取りに失敗すると、サブプロセスがブロックされ、さ​​らにはデッドロックが発生する可能性があります。

プロセスから入力ストリーム(サブプロセスの出力ストリームにパイプする)のバッファーをクリアしないと、サブプロセスがブロックされる可能性があります。

これを試して:

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

17
2つの警告:(1)ProcessBuilder + redirectErrorStream(true)を使用すると、安全です。それ以外の場合(2)Process.getInputStream()から読み取るには1つのスレッドが必要で、Process.getErrorStream()から読み取るには別のスレッドが必要です。これを理解するのに約4時間を費やしただけです(!)、別名「ハードウェイ」
kevinarpe '14

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、適切なファイルに書き出すために作成したものです。
マシューワイズ

私の場合、Springから内部的に1つのエディターを開くバッチファイルを呼び出していました。のコードを適用した後でも、コードがハングしていましたprocess.close()。しかし、上記のように入力ストリームを開いてすぐに閉じると、問題はなくなります。私の場合、Springはストリームのクローズ信号を待っていました。私はJava 8 auto closableを使用していますが。
shaILU 2017年

10

以前の回答に何かを追加したいのですが、コメントする担当者がいないため、回答を追加します。これは、JavaでプログラミングしているAndroidユーザーを対象としています。

RollingBoyからの投稿によると、このコードはほとんど私にとってうまくいきました:

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

私の場合、リターンなしのステートメント( "ip adddr flush eth0")を実行していたため、waitFor()は解放されませんでした。これを修正する簡単な方法は、常にステートメントで何かを返すようにすることです。私にとって、それは次を実行することを意味しました: "ip adddr flush eth0 && echo done"。バッファは1日中読み取ることができますが、何も返されない場合、スレッドは待機を解放しません。

それが誰かを助けることを願っています!


2
コメントする担当者がいない場合は、回避せずにコメントしてください。これをそれ自体で答えにして、それから担当者を取得してください!
モニカの訴訟に資金を供給する

出力がない場合にprocess.waitFor()ハングするのはハングアップだとは思いませんreader.readLine()。私はwaitFor(long,TimeUnit)何かがうまくいかない場合にタイムアウトを使用してみましたが、それがハングの読み取りであることがわかりました。これにより、タイムアウトバージョンでは読み取りを実行するために別のスレッドが必要になります...
osundblad

5

いくつかの可能性があります:

  1. プロセスのすべての出力を消費していません stdout
  2. プロセスのすべての出力を消費していません stderr
  3. プロセスはあなたからの入力を待っていて、それを提供していないか、またはプロセスを閉じていませんstdin
  4. プロセスはハードループで回転しています。

5

他の人が述べたように、あなたはstderrstdoutを使わなければなりません。

他の回答と比較すると、Java 1.7以降はさらに簡単です。stderrstdoutを読み取るために、自分でスレッドを作成する必要はありません。

ただ、使用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%

最後のステップは、ランタイムを使用してこのバッチファイルを実行することです。


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

}


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.