Java - 多个并发runtime.exec() InputStreams 的问题
我别无选择,只能通过对 VBScript 的多次 Runtime.exec()
调用来检索一些外部数据。我真的很讨厌这种实现,因为我失去了跨平台灵活性,但我最终可能会开发类似的 *nix 脚本来至少缓解问题。在有人问之前,我无法解决调用外部脚本来收集数据的需要。我将忍受由此带来的问题。
exec()
进程在扩展 Runnable
的自定义类中运行。它使用 BufferedReader
从 getInputStream()
读取数据。
编辑:根据要求添加了更多代码,但我不明白额外的代码是如何相关的:)我希望它有帮助,因为格式化需要一段时间!哦,如果我的代码风格很丑陋,请轻松一点,但鼓励建设性的批评...
public class X extends JFrame implements Runnable {
...
static final int THREADS_MAX = 4;
ExecutorService exec;
...
public static void main(String[] args) {
...
SwingUtilities.invokeLater(new X("X"));
} // End main(String[])
public X (String title) {
...
exec = Executors.newFixedThreadPool(THREADS_MAX);
...
// Create all needed instances of Y
for (int i = 0; i < objects.length; i++) {
Y[i] = new Y(i);
} // End for(i)
// Initialization moved here for easy single-thread testing
// Undesired, of course
for (int i = 0; i < objects.length; i++) {
Y[i].initialize(parent);
} // End for(i)
} // End X
class Y implements Runnable {
// Define variables/arrays used to capture data here
String computerName = "";
...
public Y(int rowIndex) {
row = rowIndex;
...
computerName = (String)JTable.getValueAt(row, 0);
...
exec.execute(this);
} // End Y(int)
public void run() {
// Initialize variables/arrays used to capture data here
...
// Initialization should be done here for proper threading
//initialize(parent);
} // End run()
public void initialize(Z obj) {
runTime = Runtime.getRuntime();
...
try {
process = runTime.exec("cscript.exe query.vbs " + computerName);
stdErr = process.getErrorStream();
stdIn = process.getInputStream();
isrErr = new InputStreamReader(stdErr);
isrIn = new InputStreamReader(stdIn);
brErr = new BufferedReader(isrErr);
brIn = new BufferedReader(isrIn);
while ((line = brIn.readLine()) != null) {
// Capture, parse, and store data here
...
} // End while
} catch (IOException e) {
System.out.println("Unable to run script");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stdErr.close();
stdIn. close();
isrErr.close();
isrIn. close();
brErr. close();
brIn. close();
} catch (IOException e) {
System.out.println("Unable to close streams.");
} // End try
} // End try
} // End initialize(Z)
...
} // End class Y
} // End class X
如果我单独执行命令,我会按预期收集数据。但是,如果我在类的 run()
块中执行命令(这意味着调用是并发的,正如我所希望的那样),看起来好像只生成了一个输入流,其中所有BufferedReaders
并发消费。
为了调试该问题,我在控制台上输出每个消耗的行,并以正在读取输入流的类实例为前缀。我期望类似以下的内容,了解它们可能从实例到实例都是无序的,但单个实例的行顺序将完好无损:
exec 0: Line1
exec 1: Line1
exec 2: Line1
exec 0: Line2
exec 1: Line2
exec 2: Line2
exec 0: Line3
exec 1: Line3
exec 2: Line3
...
奇怪的是我得到了第一行的预期实例数输出(Microsoft (R) Windows Script Host Version 5.7
),但在这一行之后,只有一个进程继续在输入流中生成数据,并且所有读取器随机使用这一流,例如这个例子:
exec 2: Microsoft (R) Windows Script Host Version 5.7
exec 0: Microsoft (R) Windows Script Host Version 5.7
exec 1: Microsoft (R) Windows Script Host Version 5.7
exec 0: line2
exec 1: line3
exec 2: line4
...
更糟糕的是,读取器停滞并且 readLine() 永远不会返回 null。我读到这种类型的行为可能与缓冲区大小有关,但是当我仅运行两个并发线程时,即使输出很短,它仍然表现出相同的行为。 stdErr
中没有捕获任何表明存在问题的内容。
为了查看这是否是脚本主机的限制,我创建了一个批处理文件,该文件同时START
多个脚本实例。 我应该声明这是在 Java 之外的 cmd shell 中运行的,并启动了几个自己的 shell。但是,每个并发实例都完全返回了预期结果并且表现良好。
编辑:作为另一个故障排除想法,我决定重新启用并发,但通过将以下内容插入到我的 Y.run()
块中来错开我的初始化方法:
try {
Thread.sleep((int)(Math.random() * 1200));
} catch (InterruptedException e) {
System.out.println("Can't sleep!");
} // End try
initialize(monitor);
到我的代码中。我开始看到前几行有多个输出,但它很快就会恢复为使用同一生产者的多个消费者,并且一旦第一个完成的流关闭,其余的消费者就会引发异常。下一个消费者触发 IOException: Read error
,其余的则触发 IOException: Stream closeed
!
根据 maaartinus 的说法,运行多个并发的 InputStreams 是可能的,所以现在的问题是是什么导致了不良行为?我如何独立抓取他们的输入流?如果可以避免的话,我不想只是为了处理回数据而写入临时文件。
I have no choice but to retrieve some external data by means of several Runtime.exec()
calls to a VBScript. I truly hate this implementation, as I lose my cross-platform flexibility, but I may eventually develop similar *nix scripts to at least mitigate the problem. Before anyone asks, I cannot work around the need to call an external script to gather my data. I will live with the problems that causes.
The exec()
processes are run in a custom class that extends Runnable
. It uses a BufferedReader
to read in the data from getInputStream()
.
Edit: more code added as requested, but I don't see how the extra code is relevant :) I hope it helps, because it took a while to format! Oh, and go easy on my code style if it's ugly, but constructive criticism is encouraged...
public class X extends JFrame implements Runnable {
...
static final int THREADS_MAX = 4;
ExecutorService exec;
...
public static void main(String[] args) {
...
SwingUtilities.invokeLater(new X("X"));
} // End main(String[])
public X (String title) {
...
exec = Executors.newFixedThreadPool(THREADS_MAX);
...
// Create all needed instances of Y
for (int i = 0; i < objects.length; i++) {
Y[i] = new Y(i);
} // End for(i)
// Initialization moved here for easy single-thread testing
// Undesired, of course
for (int i = 0; i < objects.length; i++) {
Y[i].initialize(parent);
} // End for(i)
} // End X
class Y implements Runnable {
// Define variables/arrays used to capture data here
String computerName = "";
...
public Y(int rowIndex) {
row = rowIndex;
...
computerName = (String)JTable.getValueAt(row, 0);
...
exec.execute(this);
} // End Y(int)
public void run() {
// Initialize variables/arrays used to capture data here
...
// Initialization should be done here for proper threading
//initialize(parent);
} // End run()
public void initialize(Z obj) {
runTime = Runtime.getRuntime();
...
try {
process = runTime.exec("cscript.exe query.vbs " + computerName);
stdErr = process.getErrorStream();
stdIn = process.getInputStream();
isrErr = new InputStreamReader(stdErr);
isrIn = new InputStreamReader(stdIn);
brErr = new BufferedReader(isrErr);
brIn = new BufferedReader(isrIn);
while ((line = brIn.readLine()) != null) {
// Capture, parse, and store data here
...
} // End while
} catch (IOException e) {
System.out.println("Unable to run script");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stdErr.close();
stdIn. close();
isrErr.close();
isrIn. close();
brErr. close();
brIn. close();
} catch (IOException e) {
System.out.println("Unable to close streams.");
} // End try
} // End try
} // End initialize(Z)
...
} // End class Y
} // End class X
If I execute the commands individually, I gather the data as I expect. However, if I execute the commands in the run()
block of the class (meaning the calls are concurrent, as I am hoping for), it appears as if only one input stream is generated, which all BufferedReaders
consume concurrently.
To debug the issue, I output each consumed line on the console prefixed by which instance of my class was reading the input stream. I expect something like the following, understanding that they may be out of order from instance-to-instance, but the line order of a single instance would be intact:
exec 0: Line1
exec 1: Line1
exec 2: Line1
exec 0: Line2
exec 1: Line2
exec 2: Line2
exec 0: Line3
exec 1: Line3
exec 2: Line3
...
What's strange is I get the expected number of instances of the very first line of the output (Microsoft (R) Windows Script Host Version 5.7
), but after this line, only one process continues to produce data in the input stream, and all readers randomly-consume this one stream, such as this example:
exec 2: Microsoft (R) Windows Script Host Version 5.7
exec 0: Microsoft (R) Windows Script Host Version 5.7
exec 1: Microsoft (R) Windows Script Host Version 5.7
exec 0: line2
exec 1: line3
exec 2: line4
...
To make matters worse, the readers stall and readLine()
never returns null. I read that this type of behavior might have something to do with the buffer size, but when I run only two concurrent threads, even with a short output, it still exhibits the same behavior. Nothing is captured in stdErr
to indicate there is a problem.
To see if this was a limitation of the script host, I created a batch file that START
s multiple instances of the script concurrently. I should state this was run outside of Java, in a cmd shell, and launches several of its own shells. However, each concurrent instance fully returned the expected results and behaved well.
Edit: As another troubleshooting idea, I decided to re-enable concurrency, but stagger my initialization method by inserting the following into my Y.run()
block:
try {
Thread.sleep((int)(Math.random() * 1200));
} catch (InterruptedException e) {
System.out.println("Can't sleep!");
} // End try
initialize(monitor);
into my code. I begin to see multiple outputs for the first few lines, but it quickly reverts to multiple consumers consuming the same producer, and as soon as the first completed stream closes, the rest of the consumers fire exceptions. The next consumer fires an IOException: Read error
, and the rest fire IOException: Stream closed
!
According to maaartinus, it IS possible to run multiple, concurrent InputStreams
, so now the question becomes what is causing the undesired behavior? How can I independently grab their input streams? I don't want to have to write to a temporary file just to process the data back in, if I can avoid it.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为你需要小心 IO 变量的范围。这是一个运行良好的快速代码,具有来自 4 个子进程的并发输入流...
上述代码的输出 (% javac MultiExec.java; java MultiExec)
如果您向我们提供您尝试的源代码,我们可以讨论它。祝您好运,- MS
=============================================== ==================================
编辑:
DN:我理解您对 1 行输出的担忧。让我们有一个小脚本...
以及上述 Java 代码的编辑版本...
comLines 已更改,并且在每个 println() 之后添加了 Thread.sleep
public class MultiExec {
这是现在的输出(编译/运行后)...
输入流工作正常,不认为我这里有问题。抱歉回复这么长。祝你一切顺利,并等待看到你的代码,- MS
I think you need to be careful about the scope of the IO variables. Here is a quick code that works perfectly well, with concurrent Input Streams from 4 child processes...
Output from the above code (% javac MultiExec.java; java MultiExec)
If you get us the source code for your attempt, we could discuss it. Good wishes, - M.S.
=============================================================================
Edit:
DN: I understand your concerns about 1-line outputs. Lets have a small script...
and an edited version of the above Java Code...
comLines have changed, and a Thread.sleep added after every println()
public class MultiExec {
Here is the output now (after compile/run) ...
The Input Streams are working just fine, don't think I have a problem here. Sorry about the reply getting so long. Wish you the best, and waiting to see your code, - M.S.
确保您在正确的范围内声明
stdErr
和stdIn
。在这种情况下,您需要在Y
中声明它们。如果您在
X
中声明它们,则每次运行以下代码时:变量将被重新分配,并且
Y
的所有实例将引用相同的流。Make sure that you are declaring
stdErr
andstdIn
in the correct scope. In this case you need to be declaring them inY
.If you are declaring them in
X
, each time you run the following code:The variables will be reassigned, and all instances of
Y
will reference the same stream.