控制台重定向在应用程序关闭之前不起作用 (Java)
Console redirection not working until application closed (Java)
首先,我这样做只是为了好玩,无论如何我都不是专业人士。所以如果我的代码有点马虎,我不会感到惊讶!
我正在尝试在 Java11 中为控制台应用程序编写 GUI 包装器。我的计划是使用 BufferedReader 从进程中捕获 stdOut 和 stdErr 并将其显示在 JTextArea 中。在使用命令行参数填充 ArrayList 后,我从我的主 GUI 线程 运行 宁这个线程。它在 Ubuntu 或 Fedora 上完美运行,但我无法在 Windows 上正常运行。当我尝试 运行 一个交叉编译的 Windows 版本的控制台应用程序时,我的应用程序仅在控制台应用程序关闭后才显示其输出。我还尝试用 C 语言替换一个非常简单的 Hello World 应用程序(通常显示 Hello,等待 5 秒,然后显示 World),这做同样的事情。但是,如果我将 ArrayList 更改为 运行 ping.exe -t 8.8.8.8,则效果很好。
我怀疑正在发生的事情是 while 循环阻塞了线程,但我不明白它在 Linux 上是如何工作的,如果我在 Windows 上使用 ping.exe。我也尝试了 Redirect stdin and stdout in Java and inheritIO mentioned in ProcessBuilder: Forwarding stdout and stderr of started processes without blocking the main thread 中的代码,但我也遇到了同样的问题。有什么想法吗?
public class RunThread extends Thread {
@Override
public void run(){
// Create process with the ArrayList we populated above
ProcessBuilder pb = new ProcessBuilder(allArgs);
pb.redirectErrorStream(true);
// Clear the console
txtConsoleOutput.setText("");
// Try to start the process
try {
Process p = pb.start();
// Get the PID of the process we just started
pid = p.pid();
// Capture the output
String cmdOutput;
BufferedReader inputStream = new BufferedReader(new InputStreamReader(p.getInputStream()));
// Get stdOut/stdErr of the process and display in the console
while ((cmdOutput = inputStream.readLine()) != null) {
txtConsoleOutput.append(cmdOutput + "\n");
}
inputStream.close();
}
catch (IOException ex) {
JOptionPane.showMessageDialog(null,
"An error (" + ex + ") occurred while attempting to run.", AppName, JOptionPane.ERROR_MESSAGE);
}
// Clear the ArrayList so we can run again with a fresh set
allArgs.clear();
}
}
Update 根据@ControlAltDel 提供的代码和@Holger 的建议,我将其重写为线程安全的(希望如此!),但最终结果是一样。
SwingWorker <Void, String> RunTV = new SwingWorker <Void, String> () {
@Override
protected Void doInBackground() {
// Create process with the ArrayList we populated above
ProcessBuilder pb = new ProcessBuilder(allArgs);
pb.directory(new File(hacktvDirectory));
pb.redirectErrorStream(true);
// Try to start the process
try {
Process p = pb.start();
// Get the PID of the process we just started
pid = p.pid();
// Capture the output
DataFetcher df = new DataFetcher(p.getInputStream(), new byte[1024], 0);
FetcherListener fl = new FetcherListener() {
@Override
public void fetchedAll(byte[] bytes) {}
@Override
public void fetchedMore(byte[] bytes, int start, int end) {
publish(new String (bytes, start, end-start));
}
};
df.addFetcherListener(fl);
new Thread(df).start();
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
} // End doInBackground
// Update the GUI from this method.
@Override
protected void done() {
// Revert button to say Run instead of Stop
changeStopToRun();
// Clear the ArrayList so we can run again with a fresh set
allArgs.clear();
}
// Update the GUI from this method.
@Override
protected void process(List<String> chunks) {
// Here we receive the values from publish() and display
// them in the console
for (String o : chunks) {
txtConsoleOutput.append(o);
txtConsoleOutput.repaint();
}
}
};
RunTV.execute();
}
2020 年 10 月 11 日更新 继 kriegaex 的帖子后,我又看了一眼。不幸的是,示例代码做了同样的事情,但是他们的评论 "例如,如果您的示例程序使用 System.out.print() 而不是 println(),您将永远不会在控制台上看到任何内容,因为输出将被缓冲。 和我一起按铃。
我可以访问我正在包装的程序的源代码,它是用 C 语言编写的。它具有以下代码以将视频分辨率打印到控制台:
void vid_info(vid_t *s)
{
fprintf(stderr, "Video: %dx%d %.2f fps (full frame %dx%d)\n",
s->active_width, s->conf.active_lines,
(double) s->conf.frame_rate_num / s->conf.frame_rate_den,
s->width, s->conf.lines
);
fprintf(stderr, "Sample rate: %d\n", s->sample_rate);
}
如果我在第二个 fprintf 语句下面添加 fflush(stderr);,我会在控制台上看到这些行,而无需修改我自己的代码中的任何内容。我仍然不明白为什么没有这个它在 Linux 中工作,但至少我知道答案。
Steeviebops:对于您最后的评论“...开始怀疑这是否可能”
是的,下面是如何使用 DataFetcher 实现它,这是一个可线程回调 reader:(您可能还想使用 System.err)(https://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/io/)
DataFetcher df = new DataFetcher(System.in, new byte[1024], 0);
FetcherListener fl = new FetcherListener() {
public void gotAll(byte[] bytes) {}
public void getMore(byte[] bytes, int start, int end) {
SwingUtilities.invokeLater(new Runnable() {
txtConsoleOutput.append(new String (bytes, start, end-start) + "\n");
// You may or may not need/want to do a repaint here
});
}
}
df.addFetcherListener(fl);
new Thread(df).start();
我自己评论的相关信息:
Another thought: Have you tried reproducing this without Swing and just dumping the stuff read from the streams onto the text console of your program? Maybe the problem that it works with ping
but not with the other test program is that the latter simply writes into a buffered stream which only gets flushed once in a while (e.g. when exiting) and hence there is nothing to read for your own program. I imagine that writing "Hello" + "world" to a stream with a buffer significantly bigger than those short strings might cause such behaviour. ping
however might write and flush directly.
例如,如果您的示例程序使用 System.out.print()
而不是 println()
,您将永远不会在控制台上看到任何内容,因为输出将被缓冲。只有在您插入 println()
- 暗示调用 BufferedWriter.flushBuffer()
- 或直接刷新编写器的缓冲区后,从第一个进程的控制台读取的其他程序才能读取某些内容。
目标应用,写入控制台:
import java.util.Random;
public class TargetApp {
public static void main(String[] args) throws InterruptedException {
System.out.print("Hello ");
Thread.sleep(1500);
System.out.println("world!");
Random random = new Random();
for (int i = 0; i < 250; i++) {
System.out.print("#");
if (random.nextInt(20) == 0)
System.out.println();
Thread.sleep(50);
}
}
}
控制器应用程序,读取目标应用程序的控制台输出:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
public class ControllerApp extends Thread {
List<String> allArgs = Arrays.asList(
//"ping", "-n", "3", "google.de"
"java", "-cp", "bin", "TargetApp"
);
@Override
public void run() {
try (
BufferedReader inputStream = new BufferedReader(
new InputStreamReader(
new ProcessBuilder(allArgs)
.redirectErrorStream(true)
.start()
.getInputStream()
)
)
) {
String cmdOutput;
while ((cmdOutput = inputStream.readLine()) != null) {
System.out.println(cmdOutput);
}
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public static void main(String[] args) throws InterruptedException {
new ControllerApp().start();
}
}
如果你 运行 ControllerApp
,你会看到它不会显示“Hello”和“world!”分别但同时。 “#”字符串也是如此。它根据其他程序的缓冲区刷新行为以块的形式读取它们。您还会注意到它正在以 stop-and-go 方式编写以下协议,而不是像每 50 毫秒连续的“#”字符流。
Hello world!
#####
#################
############
####
#############
##########################################
###############
################
#################
######
##########
######
#######
############################################
#########
#################
##########
所以如果这是你的问题,它在 TargetApp
而不是 ControllerApp
。那么它也与Swing无关。
更新: 我忘了提到您可以通过注释掉这两行来模拟仅在 TargetApp
退出后看到最后输出的行为:
// if (random.nextInt(20) == 0)
// System.out.println();
然后控制台日志看起来像这样,只有在 TargetApp
终止时才会打印长“#”行:
Hello world!
##########################################################################################################################################################################################################################################################
首先,我这样做只是为了好玩,无论如何我都不是专业人士。所以如果我的代码有点马虎,我不会感到惊讶!
我正在尝试在 Java11 中为控制台应用程序编写 GUI 包装器。我的计划是使用 BufferedReader 从进程中捕获 stdOut 和 stdErr 并将其显示在 JTextArea 中。在使用命令行参数填充 ArrayList 后,我从我的主 GUI 线程 运行 宁这个线程。它在 Ubuntu 或 Fedora 上完美运行,但我无法在 Windows 上正常运行。当我尝试 运行 一个交叉编译的 Windows 版本的控制台应用程序时,我的应用程序仅在控制台应用程序关闭后才显示其输出。我还尝试用 C 语言替换一个非常简单的 Hello World 应用程序(通常显示 Hello,等待 5 秒,然后显示 World),这做同样的事情。但是,如果我将 ArrayList 更改为 运行 ping.exe -t 8.8.8.8,则效果很好。
我怀疑正在发生的事情是 while 循环阻塞了线程,但我不明白它在 Linux 上是如何工作的,如果我在 Windows 上使用 ping.exe。我也尝试了 Redirect stdin and stdout in Java and inheritIO mentioned in ProcessBuilder: Forwarding stdout and stderr of started processes without blocking the main thread 中的代码,但我也遇到了同样的问题。有什么想法吗?
public class RunThread extends Thread {
@Override
public void run(){
// Create process with the ArrayList we populated above
ProcessBuilder pb = new ProcessBuilder(allArgs);
pb.redirectErrorStream(true);
// Clear the console
txtConsoleOutput.setText("");
// Try to start the process
try {
Process p = pb.start();
// Get the PID of the process we just started
pid = p.pid();
// Capture the output
String cmdOutput;
BufferedReader inputStream = new BufferedReader(new InputStreamReader(p.getInputStream()));
// Get stdOut/stdErr of the process and display in the console
while ((cmdOutput = inputStream.readLine()) != null) {
txtConsoleOutput.append(cmdOutput + "\n");
}
inputStream.close();
}
catch (IOException ex) {
JOptionPane.showMessageDialog(null,
"An error (" + ex + ") occurred while attempting to run.", AppName, JOptionPane.ERROR_MESSAGE);
}
// Clear the ArrayList so we can run again with a fresh set
allArgs.clear();
}
}
Update 根据@ControlAltDel 提供的代码和@Holger 的建议,我将其重写为线程安全的(希望如此!),但最终结果是一样。
SwingWorker <Void, String> RunTV = new SwingWorker <Void, String> () {
@Override
protected Void doInBackground() {
// Create process with the ArrayList we populated above
ProcessBuilder pb = new ProcessBuilder(allArgs);
pb.directory(new File(hacktvDirectory));
pb.redirectErrorStream(true);
// Try to start the process
try {
Process p = pb.start();
// Get the PID of the process we just started
pid = p.pid();
// Capture the output
DataFetcher df = new DataFetcher(p.getInputStream(), new byte[1024], 0);
FetcherListener fl = new FetcherListener() {
@Override
public void fetchedAll(byte[] bytes) {}
@Override
public void fetchedMore(byte[] bytes, int start, int end) {
publish(new String (bytes, start, end-start));
}
};
df.addFetcherListener(fl);
new Thread(df).start();
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
} // End doInBackground
// Update the GUI from this method.
@Override
protected void done() {
// Revert button to say Run instead of Stop
changeStopToRun();
// Clear the ArrayList so we can run again with a fresh set
allArgs.clear();
}
// Update the GUI from this method.
@Override
protected void process(List<String> chunks) {
// Here we receive the values from publish() and display
// them in the console
for (String o : chunks) {
txtConsoleOutput.append(o);
txtConsoleOutput.repaint();
}
}
};
RunTV.execute();
}
2020 年 10 月 11 日更新 继 kriegaex 的帖子后,我又看了一眼。不幸的是,示例代码做了同样的事情,但是他们的评论 "例如,如果您的示例程序使用 System.out.print() 而不是 println(),您将永远不会在控制台上看到任何内容,因为输出将被缓冲。 和我一起按铃。
我可以访问我正在包装的程序的源代码,它是用 C 语言编写的。它具有以下代码以将视频分辨率打印到控制台:
void vid_info(vid_t *s)
{
fprintf(stderr, "Video: %dx%d %.2f fps (full frame %dx%d)\n",
s->active_width, s->conf.active_lines,
(double) s->conf.frame_rate_num / s->conf.frame_rate_den,
s->width, s->conf.lines
);
fprintf(stderr, "Sample rate: %d\n", s->sample_rate);
}
如果我在第二个 fprintf 语句下面添加 fflush(stderr);,我会在控制台上看到这些行,而无需修改我自己的代码中的任何内容。我仍然不明白为什么没有这个它在 Linux 中工作,但至少我知道答案。
Steeviebops:对于您最后的评论“...开始怀疑这是否可能”
是的,下面是如何使用 DataFetcher 实现它,这是一个可线程回调 reader:(您可能还想使用 System.err)(https://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/io/)
DataFetcher df = new DataFetcher(System.in, new byte[1024], 0);
FetcherListener fl = new FetcherListener() {
public void gotAll(byte[] bytes) {}
public void getMore(byte[] bytes, int start, int end) {
SwingUtilities.invokeLater(new Runnable() {
txtConsoleOutput.append(new String (bytes, start, end-start) + "\n");
// You may or may not need/want to do a repaint here
});
}
}
df.addFetcherListener(fl);
new Thread(df).start();
我自己评论的相关信息:
Another thought: Have you tried reproducing this without Swing and just dumping the stuff read from the streams onto the text console of your program? Maybe the problem that it works with
ping
but not with the other test program is that the latter simply writes into a buffered stream which only gets flushed once in a while (e.g. when exiting) and hence there is nothing to read for your own program. I imagine that writing "Hello" + "world" to a stream with a buffer significantly bigger than those short strings might cause such behaviour.ping
however might write and flush directly.
例如,如果您的示例程序使用 System.out.print()
而不是 println()
,您将永远不会在控制台上看到任何内容,因为输出将被缓冲。只有在您插入 println()
- 暗示调用 BufferedWriter.flushBuffer()
- 或直接刷新编写器的缓冲区后,从第一个进程的控制台读取的其他程序才能读取某些内容。
目标应用,写入控制台:
import java.util.Random;
public class TargetApp {
public static void main(String[] args) throws InterruptedException {
System.out.print("Hello ");
Thread.sleep(1500);
System.out.println("world!");
Random random = new Random();
for (int i = 0; i < 250; i++) {
System.out.print("#");
if (random.nextInt(20) == 0)
System.out.println();
Thread.sleep(50);
}
}
}
控制器应用程序,读取目标应用程序的控制台输出:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
public class ControllerApp extends Thread {
List<String> allArgs = Arrays.asList(
//"ping", "-n", "3", "google.de"
"java", "-cp", "bin", "TargetApp"
);
@Override
public void run() {
try (
BufferedReader inputStream = new BufferedReader(
new InputStreamReader(
new ProcessBuilder(allArgs)
.redirectErrorStream(true)
.start()
.getInputStream()
)
)
) {
String cmdOutput;
while ((cmdOutput = inputStream.readLine()) != null) {
System.out.println(cmdOutput);
}
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public static void main(String[] args) throws InterruptedException {
new ControllerApp().start();
}
}
如果你 运行 ControllerApp
,你会看到它不会显示“Hello”和“world!”分别但同时。 “#”字符串也是如此。它根据其他程序的缓冲区刷新行为以块的形式读取它们。您还会注意到它正在以 stop-and-go 方式编写以下协议,而不是像每 50 毫秒连续的“#”字符流。
Hello world!
#####
#################
############
####
#############
##########################################
###############
################
#################
######
##########
######
#######
############################################
#########
#################
##########
所以如果这是你的问题,它在 TargetApp
而不是 ControllerApp
。那么它也与Swing无关。
更新: 我忘了提到您可以通过注释掉这两行来模拟仅在 TargetApp
退出后看到最后输出的行为:
// if (random.nextInt(20) == 0)
// System.out.println();
然后控制台日志看起来像这样,只有在 TargetApp
终止时才会打印长“#”行:
Hello world!
##########################################################################################################################################################################################################################################################