【问题标题】:How to deal with a Java Processes grandchilds when reading the output?读取输出时如何处理Java Processes的孙子?
【发布时间】:2018-07-26 10:04:52
【问题描述】:

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

  • Process#waitFor 已经在批处理文件启动时返回(我猜是因为我必须在可执行文件前面添加 cmd /c 并且 cmd 确实在几分之一秒后完成)
  • 使用Process#getInputStream 读取输出仅在我关闭记事本后才结束,而不是在批处理文件终止后。

有没有我一直想念的方法?更重要的是:如果有的话,我如何确定通过cmd /c 产生的进程的结束?

可重现的例子:

example.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(我也在实际应用程序中读过)不会关闭,从而阻塞了线程。

【问题讨论】:

    标签: java multithreading process stream


    【解决方案1】:

    首先非常感谢Leviand 在他们的answer 中提到了InputStream#available,这让我得到了一些看起来确实有效的东西:

    这个想法是,在我正在寻找的时间点,Process#isAlive 应该返回 false,因为 Stream 需要比 Process 进程更长的时间来处理(如果这有意义的话),而 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】:

    你正在启动 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");
    

    输出(我来自意大利):

    连接到目标VM,地址:'127.0.0.1:56607',传输: 'socket' 进程结束 Elenco del percorso delle cartell per il 卷 OSDisk Numero di serie del 卷:12DA-8173 C:。非 esistono sottocartelle

    读取结束与目标VM断开连接,地址: '127.0.0.1:56607',传输:'socket'

    进程以退出代码 0 结束

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

    这是文件夹内容:

    【讨论】:

    • 我有一些问题。首先:我再也没有得到任何输出,并且程序似乎已锁定在 Process#waitFor 上。其次:这是否会将命令导致的输出的打印推迟到执行后?但是,我还需要在其中运行诸如日志记录可执行文件之类的东西,以继续将输出传送到控制台。
    • 有趣的是,我的原始代码也没有任何输出。天知道为什么。
    • 我刚刚又试了一次,我得到了发布的输出...如果你愿意,我可以制作视频:)
    • 好吧,我至少让我的代码可以像我发布时一样再次工作。在对批处理文件进行了一些试验之后,我不得不要求您让 cd 命令将目录更改为包含几十个子目录的目录(至少我的桌面似乎包含足够的进程卡住。可能是因为流缓冲区是全面和进一步的执行是有限的?)。尽管通过这种方式我得到了输出并且程序终止,尽管记事本显示,但它仍然只是在程序完全完成之后可能并不总是给出(见我的第一条评论)。
    • @geisterfurz007 确定:我已经完成了测试,我会在我的答案中发布屏幕截图,因为我得到了奇怪的符号
    猜你喜欢
    • 1970-01-01
    • 2017-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多