使用Java

时间:2019-10-07 15:29:21

标签: java asynchronous process external

我想用java打印一个进程的实时输出;但是输出(几乎)仅在进程停止执行时可用。

换句话说,当我使用命令行运行相同的过程时,与使用Java执行该过程相比,获得的反馈(输出)更为频繁。

我测试了以下代码,每250ms打印一次输出:

private static String printStream(BufferedReader bufferedReader) {
    try {
        String line = bufferedReader.readLine();
        if(line != null) {
            System.out.println(line);
        }
        return line;
    } catch (IOException ex) {
        ex.printStackTrace();
    }
    return null;
}

public static void sendProcessStreams(Process process, SendMessage sendMessage) {
    String[] command = { "/bin/bash", "-c", "custom_process"};
    ProcessBuilder processBuilder = new ProcessBuilder(command);
    processBuilder.directory(new File("."));
    Process process = processBuilder.start();

    BufferedReader inputBufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 1);
    BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()), 1);
    System.out.println("Start reading process");
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
          @Override
          public void run() {
              String inputLine = printStream(inputBufferedReader);
              String errorLine = printStream(errorBufferedReader);
              if(inputLine == null && errorLine == null && !process.isAlive()) {
                  timer.cancel();
              }
          }
     }, 0, 250);
}

但是它只打印两行,然后等待该过程结束,然后再打印其他所有内容。

是否可以从外部流程中获得更频繁的反馈?如果没有,为什么不呢?

2 个答案:

答案 0 :(得分:1)

您的代码基本上(就从输出中读取而言)正常工作,问题肯定是出于其他原因,请构建一个可重现的最小问题(例如custom_process,为什么使用{{ 1}},当您可以使用当前线程时,...)。

无论如何,这是一个实时读取输出的示例:

Timer

输出:

final Process proc = new ProcessBuilder(
            "/bin/bash", "-c",
            "for i in `seq 1 10`; do echo $i; sleep $((i % 2)); done")
           .start();
try(InputStreamReader isr = new InputStreamReader(proc.getInputStream())) {
    int c;
    while((c = isr.read()) >= 0) {
        System.out.print((char) c);
        System.out.flush();
    }
}

答案 1 :(得分:1)

似乎您正在处理多线程问题,而不是在获取进程的输出。

我刚刚制作了这个演示类,您可以使用:

  

CommandExecTest.java

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;

public class CommandExecTest {
    public static void main(String[] args) throws InterruptedException {

        String executable = "cmd";
        String[] commandParams = {"@ping -n 5 localhost","echo \"hello world\"","exit 123"};
        boolean passCommandsAsLinesToShellExecutableAfterStartup = true;
        AsyncExecutor asyncExecutor = new AsyncExecutor(executable, commandParams,passCommandsAsLinesToShellExecutableAfterStartup);
        System.out.println("x"+"/x\tsecs in main thread \t\t status:"+asyncExecutor.runstate+" of async thread that monitors the process");
        asyncExecutor.start();//start() invokes the run() method as a detached thread
        for(int i=0;i<10;i++) {
            // you can do whatever here and the other process is still running and printing its output inside detached thread
            Thread.sleep(1000);
            System.out.println(i+"/10\tsecs in main thread \t\t status:"+asyncExecutor.runstate+" of async thread that monitors the process");
        }

        asyncExecutor.join(); // main thread has nothing to do anymore, wait till other thread that monitor other process finishes as well
        System.out.println("END OWN-PROGRAMM: 0 , END OTHER PROCESS:"+asyncExecutor.processExitcode);
        System.exit(0);
    }
}
  

Runstate.java

public static enum Runstate {
    CREATED, RUNNING, STOPPED
}
  

AsyncExecutor.java

public static class AsyncExecutor extends Thread{
    private String executable;
    private String[] commandParams;
    public ArrayList<String> linesSoFarStdout = new ArrayList<>();
    public ArrayList<String> linesSoFarStderr = new ArrayList<>();
    public Runstate runstate;
    public int processExitcode=-1;
    private boolean passCommandsAsLinesToShellExecutableAfterStartup = false;
    public AsyncExecutor(String executable, String[] commandParams) {
        this.executable=executable;
        this.commandParams=commandParams;
        this.runstate=Runstate.CREATED;
        this.passCommandsAsLinesToShellExecutableAfterStartup=false;
    }
    /**
     * if you want to run a single-process with arguments use <b>false</b> example executable="java" commandParams={"-jar","myjarfile.jar","arg0","arg1"} 
     * <p>
     * if you want to run a shell-process and enter commands afterwards use <b>true</b> example executable="cmd" commandParams={"@ping -n 5 localhost","echo \"hello world\"","exit 123"} 
     * @param executable
     * @param commandParams
     * @param passCommandsAsLinesToShellExecutableAfterStartup
     */
    public AsyncExecutor(String executable, String[] commandParams, boolean passCommandsAsLinesToShellExecutableAfterStartup) {
        this.executable=executable;
        this.commandParams=commandParams;
        this.runstate=Runstate.CREATED;
        this.passCommandsAsLinesToShellExecutableAfterStartup=passCommandsAsLinesToShellExecutableAfterStartup;
    }
    @Override
    public void run() {
        this.runstate=Runstate.RUNNING;
        // 1 start the process
        Process p = null;
        try {
            if(passCommandsAsLinesToShellExecutableAfterStartup) {
                // open a shell-like process like cmd and pass the arguments/command after opening it
                // * example:
                // * open 'cmd' (shell)
                // * write 'echo "hello world"' and press enter
                p = Runtime.getRuntime().exec(new String[] {executable});
                PrintWriter stdin = new PrintWriter( p.getOutputStream());
                for( int i = 0; i < commandParams.length; i++) { 
                    String commandstring = commandParams[i];
                    stdin.println( commandstring);
                }
                stdin.close();
            }
            else {
                // pass the arguments directly during startup to the process
                // * example:
                // * run 'java -jar myexecutable.jar arg0 arg1 ...'
                String[] execWithArgs = new String[commandParams.length+1];
                execWithArgs[0] = executable;
                for(int i=1;i<=commandParams.length;i++) {
                    execWithArgs[i]=commandParams[i-1];
                }
                p = Runtime.getRuntime().exec( execWithArgs);
            }
            // 2 print the output
            InputStream is = p.getInputStream();
            BufferedReader br = new BufferedReader( new InputStreamReader( is));

            InputStream eis = p.getErrorStream();
            BufferedReader ebr = new BufferedReader( new InputStreamReader( eis));

            String lineStdout=null;
            String lineStderr=null;
            while(p.isAlive()) {
                Thread.yield(); // *
                // * free cpu clock for other tasks on your PC! maybe even add thread.sleep(milliseconds) to free some more
                // * everytime this thread gets cpu clock it will try the following codeblock inside the while and yield afterwards for the next time it gets cpu-time from sheduler
                while( (lineStdout = br.readLine()) != null || (lineStderr = ebr.readLine()) != null) {
                    if(lineStdout!=null) {
                        System.out.println(lineStdout);
                        linesSoFarStdout.add(lineStdout);
                    }
                    if(lineStderr!=null) {
                        System.out.println(lineStderr);
                        linesSoFarStderr.add(lineStderr);
                    }
                }
            }
            // 3 when process ends
            this.processExitcode = p.exitValue();
        }
        catch(Exception e) {
            System.err.println("Something went wrong!");
            e.printStackTrace();
        }
        if(processExitcode!=0) {
            System.err.println("The other process stopped with unexpected existcode: " + processExitcode);
        }
        this.runstate=Runstate.STOPPED;
    }
}