如何解决控制台重定向在应用程序关闭之前不起作用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。我也尝试了Redirect stdin and stdout in Java中的代码和ProcessBuilder: Forwarding stdout and stderr of started processes without blocking the main thread中提到的InheritedIO,但是它们也遇到了同样的问题。有什么想法吗?
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();
}
更新10/11/2020 在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进行操作的方法:(您可能还想使用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();
,
我个人评论的相关信息:
另一个想法:您是否尝试过在不使用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!
#####
#################
############
####
#############
##########################################
###############
################
#################
######
##########
######
#######
############################################
#########
#################
##########
因此,如果这是您的问题,那么它是在TargetApp
中而不是ControllerApp
中。那么它也将与Swing无关。
更新:我忘了提到您可以通过注释掉这两行来模拟只有在TargetApp
退出后才能看到最后输出的行为:
// if (random.nextInt(20) == 0)
// System.out.println();
然后,控制台日志如下所示,仅当TargetApp
终止时才打印长的“#”行:
Hello world!
##########################################################################################################################################################################################################################################################
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。