解决了:
HovecraftFullOfEels发现了我的错误。请参阅下面有关他的回答的评论。我本应该调用start()时才在Thread对象上调用run()。在我的原始程序中,这意味着它在stderr上受阻,直到程序退出,然后立即获得所有stdout。
更新:
根据要求,我提供了一个最小的示例。这是制作人:
package producer;
public class Producer {
public static void main(String[] args) throws InterruptedException {
for (int i=0; i<10; i++) {
System.out.println(i);
System.out.flush();
Thread.sleep(1000);
}
}
}
这是消费者:
package consumer;
import java.io.IOException;
import java.io.InputStream;
public class Consumer {
static class Dumper extends Thread {
InputStream r;
boolean print;
Dumper(InputStream r, boolean print) {
this.r = r;
this.print = print;
}
@Override
public void run() {
int c;
try {
while ((c = r.read()) != -1) {
if (print) System.out.print((char)c);
}
r.close();
} catch (IOException ex) {}
}
}
public static void main(String[] args) throws Exception {
Process p = Runtime.getRuntime().exec("java -jar C:\\Users\\millerti\\Documents\
etBeansProjects\\Producer\\dist\\Producer.jar");
new Dumper(p.getErrorStream(), false).run();
new Dumper(p.getInputStream(), true).run();
p.waitFor();
}
}
预期的行为:由于生产者刷新其输出并且消费者完全不受缓冲,因此消费者应实时获取生产者的输出。
观察到的行为:消费者只有在生产者完成时才一次接收所有生产者的输出。
原始帖子:
我正在与Windows应用程序上的另一个开发人员一起工作。她编写了一个C程序(我们可以修改),该程序执行媒体转换并将进度消息(以百分比形式)打印到stdout。我正在用Java语言编写图形化前端,我希望程序捕获这些消息并更新GUI元素。
相关的C程序代码如下所示:
printf ("progress_msg: %d%%
", currentProgress);
fflush(stdout);
在Java程序中,我使用Runtime.getRuntime().exec()运行C程序,并获取C程序的stdout和stderr的原始InputStream对象。 (stderr只是被另一个线程扔掉了。)我正在解析stdout流,查找那些消息(好吧,我当时在使用BufferedReader,但是现在,我正在尝试调试它,所以我m直接与低级InputStream字节源对话。)
问题是,尽管Java端没有缓冲(据我所知),并且C程序中有一个fflush,但stdout文本仅在C程序完成时立即全部显示。这与我在命令提示符下运行消息时所发出的消息有所不同,在命令提示符下运行它时,情况有所不同。
很明显,这里有一些缓冲。 Java中的Process工具是否正在执行一些我不知道的缓冲?如果是这样,是否有办法将其关闭? Windows中的fflush(stdout)是否不能以预期的方式工作? Windows的等效功能是什么?
谢谢。
注意:我发现了两个相关的stackoverflow,但是它们不能回答这个问题。特别是,我们可以修改C程序,没有行缓冲,并且我们正在做适当的刷新(据我们所知)。请参阅我想要在Windows上实时输出Runtime.getRuntime()。exec()和Unbuffered子进程stdout。
在我看来,这是一个疯狂的猜测,但您指出:
... I am writing a graphical front-end in Java, and I would like my program to capture those messages and update a GUI element.
...
In the Java program, I'm using Runtime.getRuntime().exec() to run the C program and grabbing the raw InputStream objects for C program's stdout and stderr. ... I'm parsing the stdout stream, looking for those messages ...
The problem is that although there is no buffering on the Java side (that I know of) and there's an fflush in the C program, the stdout text appears all at once only when the C program finishes.
我再次不得不猜测,因为您遗漏了重要细节和所有代码,但是如果前端是Swing GUI,那么您所描述的症状表明您正在尝试获取有关Swing事件线程的所有信息。 如果这是Swing应用程序,则解决方案是确保在后台线程(如SwingWorker提供的线程)中读取Stream的输出,并确保对正在读取的数据进行缓冲,然后 以线程安全的方式(再次使用SwingWorker,尤其是其发布/处理方法对)在GUI中显示它。
为了获得更好的帮助,请为我们提供更好的信息和代码,最好是一个小的示例,实际上是最少的示例程序,我们可以对其进行运行,测试和改进。
编辑
我的测试程序:
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Consumer {
static class StreamGobbler implements Runnable {
private String name;
private boolean print;
private Scanner scanner;
public StreamGobbler(String name, InputStream inputStream, boolean print) {
this.name = name;
this.print = print;
scanner = new Scanner(inputStream);
}
@Override
public void run() {
System.out.printf("in %s run method%n", name);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (print) {
String output = String.format("From %s: %s%n", name, line);
System.out.printf(output);
}
}
if (scanner != null) {
scanner.close();
}
}
}
private static final String PRODUCER_PATH ="C:/Users/Pete/Documents/Fubar/Java/producer.jar";
public static void main(String[] args) {
List command = new ArrayList<>();
command.add("java.exe");
command.add("-jar");
command.add(PRODUCER_PATH);
try {
ProcessBuilder pBuilder = new ProcessBuilder(command);
Process process = pBuilder.start();
StreamGobbler errorGobbler = new StreamGobbler("Error Gobbler", process.getErrorStream(), true);
StreamGobbler inputGobbler = new StreamGobbler("Input Gobbler", process.getInputStream(), true);
new Thread(errorGobbler).start();
new Thread(inputGobbler).start();
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
您的担心是合理的。从EDT操纵UI是一个常见的陷阱。但是,在调试中,我实际上绕过了GUI。 InputStream在其自己的线程中,并且通过System.out.print()打印进度。我希望有人可能对此问题有所了解。但似乎病态必须听从您的建议,并提出一个最小的例子。完成后,我会更新我的帖子。
@TimothyMiller:您是否正确缓冲了输入?我想象您使用InputStreamReader,然后将其包装在BufferedReader中吗?例如,请看这个问题。
我最初使用的是缓冲读取器(如rgagnon.com/javadetails/java-0014.html中所示),但是存在相同的问题,因此我删除了额外的缓冲层。即使没有缓冲,问题也是一样。
@TimothyMiller:嗯,...让我测试您的代码,感谢您发布它!至少值得1+。
@TimothyMiller:嗯,...您的代码,...您在线程上调用run(),您知道那是正确的吗?如果您希望start()实际上充当后台线程,就应该始终调用它,对吗?
@TimothyMiller:那是我上面的答案。
@TimothyMiller:我发布了用于测试您的概念的代码。