Как определить точное состояние BufferedReader? - PullRequest
7 голосов
/ 23 января 2010

У меня есть BufferedReader (генерируется new BufferedReader(new InputStreamReader(process.getInputStream()))). Я совершенно новичок в понятии BufferedReader, но, на мой взгляд, у него есть три состояния:

  1. Строка ожидает чтения; вызов bufferedReader.readLine вернет эту строку мгновенно.
  2. Поток открыт, но нет ни одной строки, ожидающей чтения; вызов bufferedReader.readLine приведет к зависанию потока, пока не станет доступной строка.
  3. Поток закрыт; вызов bufferedReader.readLine вернет ноль.

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

Вызов BufferedReader.ready() не будет различать случаи (2) и (3) выше. Другими словами, если ready() возвращает false, возможно, поток просто закрыт (другими словами, мой базовый процесс завершился изящно) или может зависнуть основной процесс.

Итак, мой вопрос: как мне определить, в каком из этих трех состояний находится мой BufferedReader без фактического вызова readLine? К сожалению, я не могу просто позвонить readLine, чтобы проверить это, так как оно открывает мое приложение до зависания.

Я использую JDK версии 1.5.

Ответы [ 8 ]

2 голосов
/ 25 января 2010

Наконец я нашел решение для этого. Большинство ответов здесь основаны на потоках, но, как я указал ранее, я ищу решение, которое не требует потоков. Тем не менее, моей основой был процесс. Я обнаружил, что процессы, по-видимому, завершаются, если и выходные данные (называемые «входными»), и потоки ошибок пусты и закрыты. Это имеет смысл, если вы об этом думаете.

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

public String readLineWithTimeout(Process process, long timeout) throws IOException, TimeoutException {
  BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
  BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
  boolean finished = false;
  long startTime = 0;
  while (!finished) {
    if (output.ready()) {
      return output.readLine();
    } else if (error.ready()) {
      error.readLine();
    } else {
      try {
        process.exitValue();
        return null;
      } catch (IllegalThreadStateException ex) {
        //Expected behaviour
      }
    }
    if (startTime == 0) {
      startTime = System.currentTimeMills();
    } else if (System.currentTimeMillis() > startTime + timeout) {
      throw new TimeoutException();
    }
  }
}
2 голосов
/ 23 января 2010

Существует состояние, когда некоторые данные могут находиться в буфере, но не обязательно для заполнения строки. В этом случае ready() вернет true, но вызов readLine() заблокирует.

Вы легко сможете создавать собственные методы ready() и readLine(). Ваш ready() на самом деле попытается построить линию, и только после успешного завершения вернет true. Тогда ваш readLine() может вернуть полностью сформированную строку.

1 голос
/ 23 января 2010

Это довольно фундаментальная проблема с API блокировки ввода-вывода Java.

Я подозреваю, что вы захотите выбрать один из:

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

final BufferedReader reader = ...
ExecutorService executor = // create an executor here, using the Executors factory class.
Callable<String> task = new Callable<String> {
   public String call() throws IOException {
      return reader.readLine();
   }
};
Future<String> futureResult = executor.submit(task);
String line = futureResult.get(timeout);  // throws a TimeoutException if the read doesn't return in time

(2) Используйте java.nio вместо java.io. Это более сложный API, но он имеет неблокирующую семантику.

0 голосов
/ 24 января 2010

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

public class MyReader implements Runnable {
    private final BufferedReader reader;
    private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();
    private boolean closed = false;

    public MyReader(BufferedReader reader) {
        this.reader = reader;
    }

    public void run() {
        String line;
        while((line = reader.readLine()) != null) {
            queue.add(line);
        }
        closed = true;
    }

    // Returns true iff there is at least one line on the queue
    public boolean ready() {
        return(queue.peek() != null);
    }

    // Returns true if the underlying connection has closed
    // Note that there may still be data on the queue!
    public boolean isClosed() {
        return closed;
    }

    // Get next line
    // Returns null if there is none
    // Never blocks
    public String readLine() {
        return(queue.poll());
    }
}

Вот как это использовать:

BufferedReader b; // Initialise however you normally do
MyReader reader = new MyReader(b);
new Thread(reader).start();

// True if there is data to be read regardless of connection state
reader.ready();

// True if the connection is closed
reader.closed();

// Gets the next line, never blocks
// Returns null if there is no data
// This doesn't necessarily mean the connection is closed, it might be waiting!
String line = reader.readLine(); // Gets the next line

Существует четыре возможных состояния:

  1. Соединение открыто, данные недоступны
  2. Соединение открыто, данные доступны
  3. Соединение закрыто, данные доступны
  4. Соединение закрыто, данные недоступны

Вы можете различить их с помощью методов isClosed () и ready ().

0 голосов
/ 23 января 2010

Вы можете создать свою собственную оболочку для InputStream или InputStreamReader, которая работает на побайтном уровне, для которого ready () возвращает точные значения.

Другие ваши варианты - это многопоточность, которую можно выполнить просто (посмотрите на некоторые параллельные структуры данных, которые предлагает Java) и NIO, которая очень сложна и, вероятно, излишня.

0 голосов
/ 23 января 2010

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

Однако, это не должно быть ужасно сложно сделать это с несколькими потоками. Это шаблон, который я использую:

private static final ExecutorService worker = 
  Executors.newSingleThreadExecutor();

private static class Timeout implements Callable<Void> {
  private final Closeable target;
  private Timeout(Closeable target) {
    this.target = target;
  }
  public Void call() throws Exception {
    target.close();
    return null;
  }
}

...

InputStream stream = process.getInputStream();
Future<?> task = worker.schedule(new Timeout(stream), 5, TimeUnit.SECONDS);
/* Use the stream as you wish. If it hangs for more than 5 seconds, 
   the underlying stream is closed, raising an IOException here. */
...
/* If you get here without timing out, cancel the asynchronous timeout 
  and close the stream explicitly. */
if(task.cancel(false))
  stream.close();
0 голосов
/ 23 января 2010

Вы можете использовать InputStream.available (), чтобы увидеть, есть ли новые выходные данные процесса. Это должно работать так, как вы хотите, если процесс выводит только полные строки, но это не совсем надежно.

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

0 голосов
/ 23 января 2010

Подтвердили ли вы экспериментом свое утверждение, что ready () вернет false, даже если основной поток находится в конце файла? Потому что я не ожидал бы, что это утверждение будет правильным (хотя я не провел эксперимент).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...