【发布时间】:2011-01-20 23:32:44
【问题描述】:
我别无选择,只能通过对 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 中未捕获任何内容以表明存在问题。
为了查看这是否是脚本宿主的限制,我创建了一个批处理文件,STARTs 脚本的多个实例同时进行。 我应该声明这是在 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 closed!
根据 maaartinus 的说法,可以同时运行多个 InputStreams,所以现在问题变成了导致不良行为的原因是什么?如何独立获取他们的输入流?如果可以避免的话,我不想写一个临时文件来处理数据。
【问题讨论】:
-
So I'm confused - is Java capable of processing multiple input streams concurrently or not?当然,它是。 Windows 脚本宿主中可能存在问题,无论它是什么。我会尝试一个简单的脚本(或另一个程序)产生一个简单的输出。 -
听起来很奇怪,您的某些字段可能是静态的。你能发布更多你的课程吗?
-
To make matters worse, the readers stall and readLine() never returns null.尝试为每个流使用自己的线程,即每个进程两个线程。每个流上都有一个小缓冲区,当它填满时进程会被阻塞(你从另一个流中什么也得不到)。 -
这听起来像是特定于脚本主机的问题,您将哪些命令行开关传递给
cscript.exe? -
我在 TechNet 文章中发现了这一点。我不知道它是否适用于您的情况,但如果适用,它可能是解决方案的关键。 “在 MOM 2000 中,一次只能执行特定脚本的单个实例,这意味着您不必担心脚本的多个实例相互冲突。在 MOM 2005 中删除了该限制后,您获得了灵活性以牺牲一些额外的责任为代价。”
标签: java concurrency exec inputstream