2012-02-16 6 views
4

Javaアプリケーションは3つあり、appBはappCを起動して永遠に実行します。 appAはappBを開始し、その出力を読み取ります。 しかし、appAは決して終了しません。誰もが、なぜこれが起こり、どのように解決するのか考えています。 appAでストリームを読み取らないとハングしません。プロセスBの出力ストリームを読み込んで別のプロセスCを起動するとハングします永久に実行する

私は2つの方法で試してみました:直接

  1. 読む出力ストリーム(readViaInputStream)またはBufferedReaderの(readViaBufferedReaderを)。

彼らは動作しません、出力は次のようになります。

//私たちは、メインのアプリがハング、それらを呼び出す場合、出力は次のようになります。コールの子プロセスの前に

//

//コール後の子プロセス

//メインプログラムがハングします。

//出力しない - 「読み込みストリームが終了しました。」

readViaInputStreamでは、int n = getInIfOpen()でBufferedInputStream.fill()メソッドのfill()メソッドでハングします。read(buffer、pos、buffer.length - pos); FileInputStreamクラスのネイティブメソッドを呼び出します。

ViaBufferedReaderメソッドと同じです。

  1. 別のスレッドを使用して出力ストリームを読み取ります。
  2. それはあまりにも動作しない

、出力は次のようになります。コールの子プロセスの前に

//

//コールの子プロセスの後

//読むストリームが終了しました。

// ===>とメインプログラムは、任意の返信用

おかげで非常に多くのハング:)

コードは以下の通りである - ギョームのPoletは、次のコメントで提供されたコードを使用するように更新します。

public class MainApp { 

public static enum APP { 
    B, C; 
} 

public static void main(String[] args) throws Exception, 
     InterruptedException { 
    if (args.length > 0) { 
     APP app = APP.valueOf(args[0]); 
     switch (app) { 
     case B: 
      performB(); 
      break; 
     case C: 
      performC(); 
      break; 
     } 
     return; 
    } 
    performA(); 
} 

private static void performA() throws Exception { 
    String javaBin = "java"; 
    String[] cmdArray = { javaBin, "-cp", 
      System.getProperty("java.class.path"), MainApp.class.getName(), 
      APP.B.name() }; 
    ProcessBuilder builder = new ProcessBuilder(cmdArray); 
    builder.redirectErrorStream(true); 
    final Process process = builder.start(); 

    process.getOutputStream().close(); 
    process.getErrorStream().close(); 

    // if we call this, the main app hangs, the output would be: 
    // Before call child process 
    // After call child process 
    // the main program hangs here. 
    // Never output - "Read stream finished." 
    readViaInputStream(process); 

    // if we call this, the main app hangs, the output would be: 
    // Before call child process 
    // After call child process 
    // the main program hangs here. 
    // Never output - "Read stream finished." 

    // readViaBufferedReader(process); 

    // if we call this, the main app still hangs, the output would be: 
    // Before call child process 
    // After call child process 
    // Read stream finished. 
    // ===> and the main program hang 
    // readOutputViaAnotherThread(process); 

    System.err.println("Read stream finished."); // never come here 
} 

private static void performB() throws Exception { 
    System.out.println("Before call child process"); 
    String javaBin = "java"; 
    String[] cmdArray = { javaBin, "-cp", 
      System.getProperty("java.class.path"), MainApp.class.getName(), 
      APP.C.name() }; 
    ProcessBuilder builder = new ProcessBuilder(cmdArray); 
    Process process = builder.start(); 

    process.getInputStream().close(); 
    process.getOutputStream().close(); 
    process.getErrorStream().close(); 

    System.out.println("After call child process"); 
    System.exit(0); // no difference with or without this line. 
} 

private static void performC() throws Exception { 
    Thread thread = new Thread() { 
     @Override 
     public void run() { 
      int i = 0; 
      while (true) { 
       try { 
        Thread.sleep(60 * 2); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
       System.err.println("child " + ++i); 
      } 
     } 
    }; 
    thread.start(); 
    thread.join(); 

} 

private static void readViaInputStream(final Process process) 
     throws Exception { 

    // System.err.println("exitValue: " + process.waitFor()); 
    InputStream is = process.getInputStream(); 
    int result; 

    while ((result = is.read()) != -1) { 
     System.err.println(result); 
    } 
} 

private static void readViaBufferedReader(final Process process) 
     throws Exception { 
    BufferedReader in = new BufferedReader(new InputStreamReader(
      process.getInputStream(), "utf-8")); 
    String result = ""; 
    while ((result = in.readLine()) != null) { 
     System.err.println(result); 
    } 
} 

private static void readOutputViaAnotherThread(final Process process) 
     throws Exception { 
    class ReadOutputStreamThread extends Thread { 
     public void run() { 

      running = true; 
      try { 
       BufferedReader in = new BufferedReader(
         new InputStreamReader(process.getInputStream(), 
           "utf-8")); 
       String result = ""; 
       while (running && (result = in.readLine()) != null) { 
        System.err.println(result); 
       } 
      } catch (Exception e) { 
       e.printStackTrace(); 
      } 

     }; 

     private volatile boolean running; 

     public void shutdown() throws IOException { 
      running = false; 
      // this has no impact 
      process.getInputStream().close(); 
      interrupt(); 
     } 
    } 

    ReadOutputStreamThread readOutputThread = new ReadOutputStreamThread(); 
    // if we set this readOutputThread as daemon, it works, but the thread 
    // will remains run forever. 
    // readOutputThread.setDaemon(true); 
    readOutputThread.start(); 

    System.err.println("exitValue: " + process.waitFor()); 
    readOutputThread.shutdown(); 
} 

}

+0

javadocのthread.joinを読んだことがありますか? – Scorpion

+0

はい、子プロセスのメインスレッドは永遠に待っているようです。生成されたスレッドはデーモンではないため、join呼び出しは不要です。質問によれば、問題はappCではなく、入力ストリームから永遠に読み取るappAにあります。これは、ストリームの他の部分がストリームを閉じないために発生します。 –

+0

返信いただきありがとうございます。 私が提供したコードは実際のプロジェクトのものです。プロセスCは永遠に実行されることを意図しています。 - 唯一の違いは、私の実際のプロジェクトでは、C#アプリケーションがアプリケーションBを起動し、その出力と同じ問題を読むことです。 jdkソースコードをデバッグした後、BufferedReaderのfill()メソッドを呼び出し、最後にsun.nio.cs.StreamDecoder.read()メソッドを呼び出すBufferedReader.readLine()でハングアップするようです。 プライベートvoid fill()throws IOException { do {n = in.read(cb、dst、cb.length - dst); //これがここにかかります } while(n == 0);} これはJDKのバグかどうかわかりません。 –

答えて

0

EDIT:OKが解決策を見つけた: あなたが別のスレッドで読んInputStreamを移動し、Bが終了したときに調べるためにprocess.waitFor()を使用する必要があります。それはあなたがサブプロセスの流入からEOFを得ることは決してないと思われ、したがって入力ストリームを読むことは決して終わらない。

これは、コピー&ペーストすることができる唯一のSSCCE答えではありませんし、さらに変更せずに開始し、それはあなたの問題を再現申し訳ありません:これはおそらくです

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.io.UnsupportedEncodingException; 

public class Test5 { 

    public static enum APP { 
     B, C; 
    } 

    public static void main(String[] args) throws IOException, InterruptedException { 
     if (args.length > 0) { 
      APP app = APP.valueOf(args[0]); 
      switch (app) { 
      case B: 
       performB(); 
       break; 
      case C: 
       performC(); 
       break; 
      } 
      return; 
     } 
     performA(); 

    } 

    private static void performC() throws InterruptedException { 
     Thread thread = new Thread() { 
      @Override 
      public void run() { 
       int i = 0; 
       while (true) { 
        try { 
         Thread.sleep(60 * 2); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 
        System.err.println("child " + ++i); 
       } 
      } 
     }; 
     thread.start(); 
     thread.join(); 

    } 

    private static void performB() throws IOException { 
     System.out.println("Before call child process"); 
     String javaBin = "java"; 
     String[] cmdArray = { javaBin, "-cp", System.getProperty("java.class.path"), Test5.class.getName(), APP.C.name() }; 
     ProcessBuilder builder = new ProcessBuilder(cmdArray); 
     // Process process = 
     builder.start(); 
     System.out.println("After call child process"); 
     System.exit(0); // no differnce with or without this line. 

    } 

    private static void performA() throws IOException, UnsupportedEncodingException { 
     String javaBin = "java"; 
     String[] cmdArray = { javaBin, "-cp", System.getProperty("java.class.path"), Test5.class.getName(), APP.B.name() }; 
     ProcessBuilder builder = new ProcessBuilder(cmdArray); 
     builder.redirectErrorStream(true); 
     Process process = builder.start(); 
     BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8")); 
     String result = ""; 
     while ((result = in.readLine()) != null) { 
      System.err.println(result); 
     } 
     System.err.println("Read stream finished."); // never come here 
    } 
} 
+0

こんにちは、Guillaume Polet: 返信いただきありがとうございます。あなたが提供したコードははるかに優れていて、より洗練されています。 メインプログラムの のソリューションが、新しいスレッド(ReadOutputStreamThread)を起動して出力ストリームを読み込み、process.waitFor()を呼び出し、子プロセスが存在する場合は、ReadOutputStreamThreadを停止します。 しかし、なぜ私は古いコードがうまくいかず、JDKをデバッグしようとしていて、どうしてこのようなことが起きているのか考えています。 –

+0

申し訳ありませんが、そのスレッドをデーモンとして設定しない限り、出力を読み取る別のスレッドを使用していると動作しません。 –

+0

デーモンとしてそのスレッドを設定すると、スレッドが永遠に実行されたままになるので、受け入れられません。私はこれは、readlineがハングアップするためだと思う - ストリームを閉じることさえ動作しない。 –

4

あなたのプログラムがBufferedReader.readLine()にハングアップした場合ストリームは行末文字で終わらないためです。

継続して返すようにreadLine()を持つ2つの方法があります。

  • はEOL文字とreadLine()があるストリームが閉じます
  • その文字の前の行を返し、readLine()戻りnull

あなたのケースでは、これらの2つの可能性のいずれも真実ではなく、BufferedReader.readLine()がより多くの文字をEOL文字が後でまたは終わりに達したストリーム。だから、それはブロックされ、何が起こるか楽しみにしています。より多くのキャラクターが来ることを期待しています。次のEOL-charが来たとき、または基礎となるストリームが閉じられたときにのみ戻ってきます。

次のいずれかを試してください:

  • readLine()を使用しませんが、バイト単位EOL文字で終わる出力を生成しますプロセスを確保する方法、読み
  • する

もあることをあなたが自分でclose all std-Streamsに行ったことを認識しています。

+0

バーニー、返事をありがとう。私はコードをトレースする、それはreadLineと関連していないようだ - 私はバイトワイズの読み取りを使用しようとしたり、ストリームを読み取るために別のスレッドを使用しようと、いずれも動作しません。 - 更新されたコードをご覧ください。 –

+0

ありがとうございます。鉱山はreadlineでスタックしていましたが、出力ストリームを閉じることができませんでした。これは私のために働いた.. :) – Harinder

関連する問題