2010-01-23 3 views
7

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

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

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

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

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

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

+0

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

+0

N.B. В настоящее время я ищу другие варианты, кроме threading. Проблема заключалась в том, что мое приложение не работает в данный момент (по ряду причин, включая readLine), и я считаю, что темы очень сложны для отладки, особенно если они находятся на тайм-ауте. – Kidburla

ответ

2

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

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

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

Я подозреваю, что это может все еще заблокироваться, так как Reader # ready() указывает на наличие одного символа (или больше). Я также подозреваю, что могут быть случаи, когда все читатели сообщают false в ready(), но в канале процесса есть данные для чтения с потенциально блокирующим системным вызовом read(). –

+0

Я знаю, что на это дан ответ. Но так как я застрял именно в этой ситуации ДВАЖДЫ. Подсказка: вам нужно подождать, прежде чем пытаться читать из процесса, который вызывается. вы можете просто начать читать, когда процесс еще выполняется полностью, что означает, что ready() или readLine() могут блокировать неопределенно ... – rubmz

0

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

+0

Он, к сожалению, верный. Это одна из самых больших «gotchas» в Java IO, и мне грустно :( – ZoFreX

0

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

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

+0

Does InputStream.Доступно() различать случаи (2) и (3) в моем исходном сообщении лучше, чем ready()? Если да, почему вы говорите, что это ненадежно? – Kidburla

+0

Reader.ready() в конечном итоге использует InputStream.available(), чтобы определить, имеются ли какие-либо данные. Так что это не более или менее надежный. Проблема в том, что не гарантируется, что InputStream.available() будет сообщать о любых доступных данных, даже если они есть. Это связано с тем, что поток сам по себе не сможет узнать, есть ли что-либо доступное без блокировки. И даже если он что-то сообщает, вы не можете быть уверены, что это закончится линией перевода. Если это не закончится линией, вызов readLine в ready() == true все равно будет заблокирован. Я бы рекомендовал использовать выделенный поток. – x4u

+0

Спасибо, мне кажется, что доступный() не различает случаи (2) и (3) лучше, чем ready(), поэтому это, вероятно, не решает мою проблему. – Kidburla

1

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

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

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

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, но он имеет неблокирующую семантику.

2

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

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

+0

Я считаю, что это лучший метод, если не требуется дополнительная производительность буферизации. – ZoFreX

+0

Как это отличается от вашего решения, хотя ZoFrex? Похоже, что мой пользовательский ready() должен был вызвать некоторый уровень готовности на уровне байта() под ним, что не могло гарантировать блокировку read() или нет. Все еще открывает мое приложение до возможной зависания, если зависает процесс. – Kidburla

+0

Оглядываясь на исходный код Java, я обнаружил, что BufferedReader # ready() обязательно консервативен, поскольку он проверяет собственный буфер и базовый читатель, такой как InputStreamReader. http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/io/BufferedReader.java Я предполагаю, что последний InputStreamReader будет сообщать о состоянии ready() консервативно , –

0

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

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

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

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

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

+0

Это звучит как мой лучший вариант (аналогично ответу lavinio), но когда вы говорите: «ready() возвращает точные значения», как будет() различать мои случаи (2) и (3)? – Kidburla

+0

Кстати, InputStream не поддерживает метод ready(), а InputStreamReader поддерживает его, но javadoc все еще говорит «Обратите внимание, что возвращение false не гарантирует, что следующее чтение будет заблокировано». – Kidburla

+0

Если он возвращает true, это гарантирует, что следующее чтение не будет блокироваться. Я думаю. Я не знаю, как отличить случаи 2 и 3, и может только предложить проверить его! – ZoFreX

0

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

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. Connection открыт, данные доступно
  3. Соединение закрыто, данные доступны
  4. Соединение закрыто, никакие данные не доступны

Вы можете различить их с IsClosed() и готовых() методов.

Смежные вопросы