プロセス終了時のonExitメソッドを使用したプロセスの処理

Process.onExitメソッドとProcessHandle.onExitメソッドによってCompletableFutureインスタンスが返されます。これを使用してプロセス終了時のタスクをスケジュールできます。または、アプリケーションでプロセスが終了するまで待機する場合は、onExit().get()をコールします。

次の引用では、startProcessesTestメソッドによって3つのプロセスが作成され、起動されます。その後、各プロセスでonExit().thenAccept(onExitMethod)がコールされます。onExitMethodによってプロセスID (PID)、終了ステータスおよびプロセスの出力が表示されます。

public class ProcessTest {

  // ...

  static public void startProcessesTest() throws IOException, InterruptedException {
    List<ProcessBuilder> greps = new ArrayList<>();
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"java\" *"));
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"Process\" *"));
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"onExit\" *"));
    ProcessTest.startSeveralProcesses (greps, ProcessTest::printGrepResults);      
    System.out.println("\nPress enter to continue ...\n");
    System.in.read();  
  }

  static void startSeveralProcesses (
    List<ProcessBuilder> pBList,
    Consumer<Process> onExitMethod)
    throws InterruptedException {
    System.out.println("Number of processes: " + pBList.size());
    pBList.stream().forEach(
      pb -> {
        try {
          Process p = pb.start();
          System.out.printf("Start %d, %s%n",
            p.pid(), p.info().commandLine().orElse("<na>"));
          p.onExit().thenAccept(onExitMethod);
        } catch (IOException e) {
          System.err.println("Exception caught");
          e.printStackTrace();
        }
      }
    );
  }
  
  static void printGrepResults(Process p) {
    System.out.printf("Exit %d, status %d%n%s%n%n",
      p.pid(), p.exitValue(), output(p.getInputStream()));
  }

  private static String output(InputStream inputStream) {
    String s = "";
    try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
      s = br.lines().collect(Collectors.joining(System.getProperty("line.separator")));
    } catch (IOException e) {
      System.err.println("Caught IOException");
      e.printStackTrace();
    }
    return s;
  }

  // ...
}

startProcessesTestメソッドによって次のように出力されます。プロセスの終了順序が開始順序と異なる場合があることに注意してください。

Number of processes: 3
Start 12401, /bin/sh -c grep -c "java" *
Start 12403, /bin/sh -c grep -c "Process" *
Start 12404, /bin/sh -c grep -c "onExit" *

Press enter to continue ...

Exit 12401, status 0
ProcessTest.class:0
ProcessTest.java:16

Exit 12404, status 0
ProcessTest.class:0
ProcessTest.java:8

Exit 12403, status 0
ProcessTest.class:0
ProcessTest.java:38

このメソッドはSystem.in.read()メソッドをコールして、すべてのプロセスが終了(およびthenAcceptメソッドで指定されたメソッドを実行)する前にプログラムが終了することを防ぎます。

残りのプログラム処理に進む前にプロセスが終了するまで待機する場合は、onExit().get()をコールします。

  static void startSeveralProcesses (
    List<ProcessBuilder> pBList, Consumer<Process> onExitMethod)
    throws InterruptedException {
    System.out.println("Number of processes: " + pBList.size());
    pBList.stream().forEach(
      pb -> {
        try {
          Process p = pb.start();
          System.out.printf("Start %d, %s%n",
            p.pid(), p.info().commandLine().orElse("<na>"));
          p.onExit().get();
          printGrepResults(p);          
        } catch (IOException|InterruptedException|ExecutionException e ) {
          System.err.println("Exception caught");
          e.printStackTrace();
        }
      }
    );
  }

次に示すように、ComputableFutureクラスには様々なメソッドが用意されており、これをコールしてプロセス終了時のタスクをスケジュールできます。

  • thenApply: 関数タイプのラムダ式(値を返すラムダ式)を使用する点以外はthenAcceptと同じです。

  • thenRun: 実行可能タイプの(仮パラメータまたは戻り値がない)ラムダ式を使用します。

  • thenApplyAsyc: ForkJoinPool.commonPool()からのスレッドで指定された関数を実行します。

ComputableFutureではFutureインタフェースが実装されるため、このクラスにも同期メソッドが含まれています。

  • get​(long timeout, TimeUnit unit): 必要に応じてプロセスが終了するまで(引数で指定された時間まで)待機します。

  • isDone: プロセスが完了している場合はtrueを返します。