Основная программа зависает при чтении потока вывода processB, который запускает другой процесс C, который выполняется вечно - PullRequest
4 голосов
/ 16 февраля 2012

Существует три Java-приложения, appB просто запускает appC, который будет работать вечно. appA запускает appB и читает его вывод. Но appA никогда не выходит. У любого есть идея, почему это происходит и как ее решить. Он не зависает, если я не читаю поток в appA.

Я пробовал два способа:

  1. Считывание выходного потока (readViaInputStream) или BufferedReader (readViaBufferedReader) напрямую.

Они не работают, результат будет:

// если мы их вызываем, основное приложение зависает, вывод будет:

// Перед вызовом дочернего процесса

// После вызова дочернего процесса

// здесь висит основная программа.

// Никогда не выводить - «Чтение потока завершено».

В readViaInputStream он висит при методе fill () в методе BufferedInputStream.fill () при int n = getInIfOpen (). Read (buffer, pos, buffer.length - pos); Он вызывает собственный метод класса FileInputStream.

То же, что и для метода readViaBufferedReader.

  1. Используйте другой поток для чтения выходного потока.

Это тоже не работает, вывод будет:

// Перед вызовом дочернего процесса

// После вызова дочернего процесса

// Считывание закончено.

// ===> и основная программа зависает

Большое спасибо за любой ответ:)

Код следующий: обновлен для использования кода Гийома Полета, предоставленного в следующем комментарии.

public class MainApp {

public static enum APP {
    B, C;
}

public static void main(String[] args) throws Exception,
        InterruptedException {
    if (args.length > 0) {
        APP app = APP.valueOf(args[0]);
        switch (app) {
        case B:
            performB();
            break;
        case C:
            performC();
            break;
        }
        return;
    }
    performA();
}

private static void performA() throws Exception {
    String javaBin = "java";
    String[] cmdArray = { javaBin, "-cp",
            System.getProperty("java.class.path"), MainApp.class.getName(),
            APP.B.name() };
    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    builder.redirectErrorStream(true);
    final Process process = builder.start();

    process.getOutputStream().close();
    process.getErrorStream().close();

    // if we call this, the main app hangs, the output would be:
    // Before call child process
    // After call child process
    // the main program hangs here.
    // Never output - "Read stream finished."
    readViaInputStream(process);

    // if we call this, the main app hangs, the output would be:
    // Before call child process
    // After call child process
    // the main program hangs here.
    // Never output - "Read stream finished."

    // readViaBufferedReader(process);

    // if we call this, the main app still hangs, the output would be:
    // Before call child process
    // After call child process
    // Read stream finished.
    // ===> and the main program hang
    // readOutputViaAnotherThread(process);

    System.err.println("Read stream finished."); // never come here
}

private static void performB() throws Exception {
    System.out.println("Before call child process");
    String javaBin = "java";
    String[] cmdArray = { javaBin, "-cp",
            System.getProperty("java.class.path"), MainApp.class.getName(),
            APP.C.name() };
    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    Process process = builder.start();

    process.getInputStream().close();
    process.getOutputStream().close();
    process.getErrorStream().close();

    System.out.println("After call child process");
    System.exit(0); // no difference with or without this line.
}

private static void performC() throws Exception {
    Thread thread = new Thread() {
        @Override
        public void run() {
            int i = 0;
            while (true) {
                try {
                    Thread.sleep(60 * 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.err.println("child " + ++i);
            }
        }
    };
    thread.start();
    thread.join();

}

private static void readViaInputStream(final Process process)
        throws Exception {

    // System.err.println("exitValue: " + process.waitFor());
    InputStream is = process.getInputStream();
    int result;

    while ((result = is.read()) != -1) {
        System.err.println(result);
    }
}

private static void readViaBufferedReader(final Process process)
        throws Exception {
    BufferedReader in = new BufferedReader(new InputStreamReader(
            process.getInputStream(), "utf-8"));
    String result = "";
    while ((result = in.readLine()) != null) {
        System.err.println(result);
    }
}

private static void readOutputViaAnotherThread(final Process process)
        throws Exception {
    class ReadOutputStreamThread extends Thread {
        public void run() {

            running = true;
            try {
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(process.getInputStream(),
                                "utf-8"));
                String result = "";
                while (running && (result = in.readLine()) != null) {
                    System.err.println(result);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        };

        private volatile boolean running;

        public void shutdown() throws IOException {
            running = false;
            // this has no impact
            process.getInputStream().close();
            interrupt();
        }
    }

    ReadOutputStreamThread readOutputThread = new ReadOutputStreamThread();
    // if we set this readOutputThread as daemon, it works, but the thread
    // will remains run forever.
    // readOutputThread.setDaemon(true);
    readOutputThread.start();

    System.err.println("exitValue: " + process.waitFor());
    readOutputThread.shutdown();
}

}

Ответы [ 2 ]

4 голосов
/ 17 февраля 2012

Когда ваша программа зависает в BufferedReader.readLine(), это, скорее всего, связано с тем, что поток не заканчивается символом конца строки.

Существует два способа получить readLine() для продолжения и возврата:

  • есть символ EOL и readLine() возвращает строку перед этим символом
  • поток закрывается и readLine() возвращает null

В вашем случае ни одна из этих двух возможностей не верна, и у BufferedReader.readLine() нет способа решить, появятся ли в потоке больше символов с символом EOL позже или достигнут конец.Так что он блокирует и смотрит вперед, что произойдет.Он ожидает, что поступит больше символов, и вернется только тогда, когда придет следующий EOL-символ или будет закрыт основной поток.

Попробуйте выполнить одно из следующих действий:

  • не использоватьreadLine() но побайтный метод чтения
  • гарантирует, что процесс выдаст вывод, заканчивающийся символом EOL

Также имейте в виду, что вам нужно закрыть всеstd-Streams самостоятельно - даже если не используется.

1 голос
/ 16 февраля 2012

РЕДАКТИРОВАТЬ: ОК нашел решение: Вы должны переместить чтение входного потока в другой поток и использовать process.waitFor(), чтобы узнать, когда выйдет B. Кажется, что вы никогда не получите EOF из потока подпроцесса, и поэтому чтение входного потока никогда не заканчивается.

Извините, это не ответ, а только SSCCE, который можно скопировать и запустить без каких-либо дальнейших изменений и который воспроизводит вашу проблему:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

public class Test5 {

    public static enum APP {
        B, C;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        if (args.length > 0) {
            APP app = APP.valueOf(args[0]);
            switch (app) {
            case B:
                performB();
                break;
            case C:
                performC();
                break;
            }
            return;
        }
        performA();

    }

    private static void performC() throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                int i = 0;
                while (true) {
                    try {
                        Thread.sleep(60 * 2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.err.println("child " + ++i);
                }
            }
        };
        thread.start();
        thread.join();

    }

    private static void performB() throws IOException {
        System.out.println("Before call child process");
        String javaBin = "java";
        String[] cmdArray = { javaBin, "-cp", System.getProperty("java.class.path"), Test5.class.getName(), APP.C.name() };
        ProcessBuilder builder = new ProcessBuilder(cmdArray);
        // Process process =
        builder.start();
        System.out.println("After call child process");
        System.exit(0); // no differnce with or without this line.

    }

    private static void performA() throws IOException, UnsupportedEncodingException {
        String javaBin = "java";
        String[] cmdArray = { javaBin, "-cp", System.getProperty("java.class.path"), Test5.class.getName(), APP.B.name() };
        ProcessBuilder builder = new ProcessBuilder(cmdArray);
        builder.redirectErrorStream(true);
        Process process = builder.start();
        BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));
        String result = "";
        while ((result = in.readLine()) != null) {
            System.err.println(result);
        }
        System.err.println("Read stream finished."); // never come here
    }
}
...