Как определить точное состояние BufferedReader?

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

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

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

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

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

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

8 ответов


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

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


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

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

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();
    }
  }
}

Это довольно фундаментальная проблема с блокировкой 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, но он имеет неблокирующую семантику.


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


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

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


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

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

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();

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

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


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

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 ().