java - 控制台重定向在应用程序关闭之前不起作用(Java)
问题描述
首先,我这样做只是为了好玩,无论如何我都不是专业人士。因此,如果我的代码有点草率,我不会感到惊讶!
我正在尝试在 Java 11 中为控制台应用程序编写 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。我还尝试了Java 中的 Redirect stdin 和 stdout以及 ProcessBuilder中提到的 inheritIO 中的代码: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();
}
}
更新基于@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);
}
如果我添加fflush(stderr); 在第二个 fprintf 语句下方,我在控制台上看到了这些行,而没有在我自己的代码中修改任何东西。我仍然不明白为什么它在没有这个的情况下在 Linux 中工作,但至少我知道答案。
解决方案
我自己的评论的相关信息:
另一个想法:您是否尝试过在不使用 Swing 的情况下复制它并将从流中读取的内容转储到程序的文本控制台上?也许它适用
ping
于其他测试程序但不适用于其他测试程序的问题是后者只是写入一个缓冲流,该流只会偶尔刷新一次(例如退出时),因此您自己的程序没有什么可读取的。我想将“Hello”+“world”写入缓冲区明显大于那些短字符串的流可能会导致这种行为。ping
但是可能会直接写入和刷新。
例如,如果您的示例程序使用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!”。分开但同时。“#”字符串也是如此。它根据其他程序的缓冲区刷新行为以块的形式读取它们。您还会注意到它以走走停停的方式编写以下协议,而不是每 50 毫秒连续的“#”字符流。
Hello world!
#####
#################
############
####
#############
##########################################
###############
################
#################
######
##########
######
#######
############################################
#########
#################
##########
因此,如果这是您的问题,那么它是 inTargetApp
而不是 in ControllerApp
。那么它也将与 Swing 无关。
更新:TargetApp
我忘了提到,您可以通过注释掉这两行来模拟仅在退出后看到最后一个输出的行为:
// if (random.nextInt(20) == 0)
// System.out.println();
TargetApp
然后控制台日志看起来像这样,只有在终止时才打印长的“#”行:
Hello world!
##########################################################################################################################################################################################################################################################
推荐阅读
- android - 如果 ImageView 包含在不覆盖整个屏幕的 ViewPager 的片段中,是否可以使 ImageView 覆盖整个屏幕?
- c++11 - 使用 sscanf 解析 UUID
- ansible - 如何更轻松地将所有任务委派到 Ansible 中的某个角色中?
- postgresql - 在 MacOS 下使用 MacPorts 安装 PostgreSQL 12 / PostGIS 3.0 时,如何使用 postgis_restore.pl 脚本?
- spring - na] Caused by: java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist - Spring Batch
- list - Returning all elements from a Map[List()] in Scala
- python - json_normalize CSV 内的嵌套 json 对象
- python - pythagorean triplet prints all numbers
- c# - 如何指定在一个解决方案中运行哪个程序?
- android - 如何在键盘上显示建议栏