У меня нет выбора, кроме как извлечь некоторые внешние данные с помощью нескольких Runtime.exec()
вызовов VBScript. Я действительно ненавижу эту реализацию, потому что теряю свою межплатформенную гибкость, но в конечном итоге я могу разработать похожие * nix-скрипты, чтобы хотя бы смягчить проблему. Прежде чем кто-либо спросит, я не могу обойти необходимость вызова внешнего скрипта для сбора моих данных. Я буду жить с проблемами, которые вызывают.
Процессы exec()
выполняются в пользовательском классе, который расширяет Runnable
. Для чтения данных из getInputStream()
.
используется
BufferedReader
.
Редактировать : добавлено больше кода в соответствии с запросом, но я не понимаю, насколько уместен дополнительный код :) Надеюсь, это поможет, потому что форматирование заняло некоторое время! Да, и спокойно относитесь к моему стилю кода, если он уродлив, но конструктивная критика приветствуется ...
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()
никогда не возвращает ноль. Я читал, что этот тип поведения может иметь отношение к размеру буфера, но когда я запускаю только два параллельных потока, даже с коротким выводом, он все равно демонстрирует то же поведение. В stdErr
ничего не записано, чтобы указать, что есть проблема.
Чтобы проверить, не является ли это ограничением хоста скрипта, я создал пакетный файл, который одновременно START
копирует несколько экземпляров скрипта. Я должен заявить, что он был запущен за пределами Java в оболочке cmd и запускает несколько собственных оболочек. Однако каждый параллельный экземпляр полностью возвращал ожидаемые результаты и вел себя хорошо.
Редактировать: В качестве еще одной идеи устранения неполадок, я решил снова включить параллелизм, но пошатнуть мой метод инициализации, вставив следующее в мой 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
!
Согласно Мааартину, возможно запускать несколько одновременных InputStreams
, поэтому теперь возникает вопрос, что вызывает нежелательное поведение? Как я могу независимо захватить их входные потоки? Я не хочу писать во временный файл только для того, чтобы обработать данные обратно, если я могу избежать этого.