代码之家  ›  专栏  ›  技术社区  ›  geisterfurz007 user3764748

如何在读取输出时处理Java进程的孙子?

  •  2
  • geisterfurz007 user3764748  · 技术社区  · 7 年前

    在命令行的JavaFX模型上工作时,我遇到了以下问题:
    如果我运行的进程(例如批处理文件)运行另一个进程(例如用一个简单的 start notepad )我似乎无法正确确定批处理文件何时完成执行:

    • Process#waitFor 批处理文件启动时已经返回(我想是因为我必须添加 cmd /c 在可执行文件前面,cmd实际上是在一小段时间后完成的)
    • 使用 Process#getInputStream 只有在我关闭记事本之后结束,而不是在批处理文件终止之后。

    有没有一种方法我一直没有找到?更重要的是:我如何确定生成过程的结束 命令/命令 如果有的话?

    可复制示例:

    示例.bat:

    @echo off
    start notepad
    REM You can change the path to something else but it should be something where tree produces a longer output to reproduce the problem.
    cd %USERPROFILE%\Desktop
    tree
    

    Java代码:

    import java.io.IOException;
    import java.io.InputStream;
    
    public class Main {
    
        public static void main(String[] args) {
            Process myProcess = null;
            try {
                myProcess = Runtime.getRuntime().exec("cmd /c C:\\Users\\geisterfurz007\\Desktop\\example.bat");
            } catch (IOException e) {
                e.printStackTrace();
            }
            startReadingThread(myProcess);
    
            try {
                myProcess.waitFor();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Process ended");
        }
    
        private static void startReadingThread(Process myProcess) {
            InputStream stream = myProcess.getInputStream();
            new Thread(() -> {
                int character;
                try {
                    while ((character = stream.read()) != -1) {
                        System.out.write(character);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        System.out.println("Reading ended");
                        stream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    
    }
    

    开始打印树,中间打印“进程结束”写入控制台。最后打印“阅读结束” 之后 我关上记事本窗口。
    我的目标是找到在关闭记事本时忽略树完成打印(即批处理文件完成处理)的点。

    基于 Leviands's answer 我试图关闭进程流,一旦它被执行无效。
    不幸的是,InputStream再次关闭中间内容,而ErrorStream(我在实际应用程序中也读到了)不会关闭,因此阻塞了线程。

    2 回复  |  直到 7 年前
        1
  •  2
  •   geisterfurz007 user3764748    6 年前

    首先非常感谢 Leviand 谁提到 InputStream#available 在他们 answer 这让我得到了一个看起来很有用的东西:

    其思想是,在我正在寻找的时间点上,Process#isAlive应该返回false,因为流处理的时间比进程处理的时间长(如果这有意义的话),而InputStream中不应该有可读取的字符,因此InputStreamාavailable应该返回0。

    这导致了这段代码:

    import java.io.IOException;
    import java.io.InputStream;
    
    public class Main {
    
        public static void main(String[] args) {
            Process myProcess = null;
            try {
                myProcess = Runtime.getRuntime().exec("cmd /c C:\\Users\\geisterfurz007\\Desktop\\example.bat");
            } catch (IOException e) {
                e.printStackTrace();
            }
            startReadingThread(myProcess).start();
        }
    
        private static Thread startReadingThread(Process myProcess) {
            InputStream stream = myProcess.getInputStream();
            return new Thread(() -> {
                int character;
                try {
                    while (myProcess.isAlive() || stream.available() > 0) {
    
                        if ((character = stream.read()) == -1) {
                            break;
                        }
                        System.out.write(character);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        stream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
    }
    

    有了上面的代码,我可以从进程流中读取,而忽略任何孙子进程。


    经过一段时间的使用,有一些事情需要解决:

    • 我现在开始使用InputStreamReader有一个主要原因:我可以指定编码。所有发生的 stream.available() > 0 那就得换成 reader.ready() .
    • 这会在空闲时消耗大量资源!如果在尝试再次读取之前没有要读取的内容,那么让线程休眠几毫秒是有意义的。
    • 至少在我的用例中,我将每个字符一个一个地发送到我的GUI,这会很快杀死GUI以获得更长的输出。在对主线程进行进一步处理之前,请考虑输出的某种缓冲区。
        2
  •  1
  •   Leviand    7 年前

    你在发射 startReadingThread(myProcess); ,那你就用 myProcess.waitFor(); 等待那个过程 myProcess 在打印前结束 System.out.println("Reading ended"); ,这与你愿意做的相反。

    应该“阻止”另一个进程开始的是 startReadingThread .

    这个问题也出现在while循环中,这是不正确的。 我想换一些东西 stream.available() != 0 ,但它可以用于测试):

        private static Thread startReadingThread(Process myProcess) {
        InputStream stream = myProcess.getInputStream();
        return new Thread(() -> {
            int character;
            try {
                while (stream.available() != 0) {
                    if((character = stream.read()) == -1) {
                        System.out.write(character);
                        stream.close();
                        break;
                    }
                    System.out.write(character);
                }
                stream.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    System.out.println("Reading ended");
                    stream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    

    然后使用以下命令编辑主菜单:

    public static void main(String[] args) {
        Process myProcess = null;
        try {
            myProcess = Runtime.getRuntime().exec("cmd /c C:\\Users\\geisterfurz007\\Desktop\\example.bat");
               } catch (IOException e) {
            e.printStackTrace();
        }
    
        try {
            if(myProcess.waitFor() == 0){
                startReadingThread(myProcess).start();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Process ended");
    

    输出(我来自意大利):

    已连接到目标虚拟机,地址:“127.0.0.1:56607”,传输: '插座' 进程已结束 每个il的percorso delle cartelle Elenco del percorso delle cartelle 卷OSDisk Numero di serie del卷:12DA-8173 C:。非埃希斯托诺 索托卡特尔

    阅读结束 与目标虚拟机断开连接,地址: '127.0.0.1:56607',传输:'socket'

    退出代码0完成的进程

    编辑: 这是一个文件夹的测试,里面有子文件夹(我附上了奇怪符号的截图)

    test result

    这是文件夹内容:

    folder content