Одновременное чтение входных потоков и потоков ошибок с использованием зависаний BufferedReaders

Во-первых, позвольте мне извиниться перед сообществом SO за то, что я пришел к вам с чем-то таким тривиальным. Но я был в этом весь день, и я в конце моей веревки.

В моей программе есть раздел, который требует извлечения текста из входного потока и потока ошибок из процесса, который запускается с помощью Runtime.getrunTime().exec(), и передачи его на стандартный ввод и вывод упорядоченным образом. У меня есть функция, которая, насколько я могу судить, должна работать. Но, похоже, он попадает в ловушку-22, когда он ждет, пока поток сообщит о готовности, но поток завершился и не сообщает. Я сбит с толку. Я не могу придумать другого способа сделать это, который соответствует моим ограничениям, и я довольно скептически отношусь к тому, что такая уловка-22 может существовать.

Вот мой код:

private void forwardStreamtoStd(InputStream in, InputStream err) 
throws IOException {
    int c = -1;
    BufferedReader inReader = new BufferedReader(
        new InputStreamReader(in, "US-ASCII"));
    BufferedReader errReader = new BufferedReader(
        new InputStreamReader(err, "US-ASCII"));
    boolean inFinished = false, errFinished = false;

    try {
        System.out.println("Begin stream read loop...");
        while (!inFinished && !errFinished) {
        if (!inFinished) {
            while (inReader.ready()) {
                if ((c = inReader.read()) == -1) {
                    inFinished = true;
                } 
                else {
                    System.out.print((char) c);
                }
            }
        }

        if (!errFinished) {
            while (errReader.ready()) {
                if ((c = errReader.read()) == -1) {
                    errFinished = true;
                } 
                else {
                     System.err.print((char) c);
                }
            }
        }
        }
        System.out.println("End stream read loop.");
    } 
    catch (IOException e) {
        throw e;
    } 
    finally {
        errReader.close();
        inReader.close();
    }
}

Проблема, по-видимому, заключается в том, что циклы чтения ждут, пока потоки сообщат о готовности, и в результате не видят -1, возвращаемое чтением, сообщающее им, что пришло время выйти. Я пытаюсь избежать блокировки любого потока, чтобы я мог по очереди извлекать из обоих, когда они будут готовы. Однако как я могу поймать конец потока процесса? Я что-то упускаю? Не следует читать отчет о том, что он прочитан, когда у него есть конец потока -1? Процессы заканчиваются, поэтому их потоки должны умирать. Что я здесь делаю неправильно?


person Daniel Bingham    schedule 28.08.2009    source источник


Ответы (4)


http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4090471

Решение, которое я всегда использовал, состоит в том, чтобы создать отдельный поток для чтения одного из потоков, присоединиться к потоку, когда основной поток закончит чтение, а затем дождаться процесса.

person Brett Kail    schedule 28.08.2009

Есть еще две возможности:

  • Используйте ProcessBuilder и вызовите redirectErrorStream(true), чтобы объединить два потока, и вам нужно прочитать один поток. У меня есть пример здесь.
  • В JDK7 вы могли вызвать inheritIO() для автоматической пересылки всего

Изменить Во втором случае кажется, что вызов ready() вводит вашу программу в заблуждение. Попробуй это:

private void forwardStreamtoStd(InputStream in, InputStream err) 
throws IOException {
    int c = -1;
    BufferedReader inReader = new BufferedReader(
        new InputStreamReader(in, "US-ASCII"));
    BufferedReader errReader = new BufferedReader(
        new InputStreamReader(err, "US-ASCII"));
    boolean inFinished = false, errFinished = false;

    try {
        System.out.println("Begin stream read loop...");
        if (!inFinished) {
            while ((c = inReader.read()) != -1) {
                    System.out.print((char) c);
            }
            inFinished = true;
        }

        if (!errFinished) {
            while ((c = errReader.read()) != -1) {
                System.err.print((char) c);
            }
            errFinished = true;
        }
        System.out.println("End stream read loop.");
    } 
    catch (IOException e) {
        throw e;
    } 
    finally {
        errReader.close();
        inReader.close();
    }
}

Или еще лучше, оставьте BufferedReader, если вы не планируете никаких дополнительных преобразований:

private void createReader(final InputStream in, final OutputStream out) {
    new Thread() {
        public void run() {
            try {
                int c = 0;
                while ((c = in.read()) != -1) {
                    out.write(c);
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            } finally {
                in.close();
            }
        }
    }.start();
}
private void forwardStreamtoStd(InputStream in, InputStream err) 
throws IOException {
    createReader(in, System.out);
    createReader(err, System.err);
}
person akarnokd    schedule 28.08.2009

важно потреблять 2 потока одновременно, чтобы предотвратить блокировку. См. эту статью. больше информации и, в частности, обратите внимание на механизм StreamGobbler, который фиксирует stdout/err в отдельных потоках.

person Brian Agnew    schedule 28.08.2009

Если я правильно помню, порожденный процесс никогда не закроет поток, поэтому вам нужно будет иметь читатели в своих собственных потоках, спать в основном потоке, пока процесс не завершится, а затем закрыть читатели.

person aperkins    schedule 28.08.2009