2010-01-23 15 views
7

Tengo un BufferedReader (generado por new BufferedReader(new InputStreamReader(process.getInputStream()))). Soy bastante nuevo en el concepto de un BufferedReader pero como yo lo veo, tiene tres estados:¿Cómo determinar el estado exacto de un BufferedReader?

  1. Una línea está esperando para ser leído; llamando al bufferedReader.readLine devolverá esta cadena al instante.
  2. La transmisión está abierta, pero no hay línea esperando a ser leída; llamando al bufferedReader.readLine colgará el hilo hasta que una línea esté disponible.
  3. La secuencia está cerrada; llamando al bufferedReader.readLine devolverá nulo.

Ahora quiero determinar el estado de BufferedReader, de modo que pueda determinar si puedo leerlo sin tener que colgar la aplicación. El proceso subyacente (ver arriba) es notoriamente poco confiable y, por lo tanto, podría haberse colgado; en este caso, no quiero que cuelgue mi aplicación host. Por lo tanto, estoy implementando un tipo de tiempo de espera. Intenté hacer esto primero con el enhebrado, pero se volvió terriblemente complicado.

Llamando BufferedReader.ready() no distinguirá entre los casos (2) y (3) anteriores. En otras palabras, si ready() devuelve falso, es posible que la secuencia se haya cerrado (en otras palabras, mi proceso subyacente se haya cerrado correctamente) o que el proceso subyacente se haya bloqueado.

Así que mi pregunta es: ¿cómo puedo determinar cuál de estos tres estados es BufferedReader sin llamar realmente al readLine? Lamentablemente, no puedo simplemente llamar al readLine para verificar esto, ya que abre mi aplicación hasta el cuello.

Estoy utilizando la versión 1.5 de JDK.

+0

Por cierto, estoy asumiendo por el momento que mi proceso subyacente no puede colgar en el medio de una línea .... aunque eso puede ser una suposición errónea, no ha ocurrido hasta ahora durante las pruebas y tengo suficientes otros problemas !! – Kidburla

+0

N.B. Actualmente estoy buscando otras opciones que enhebrar. El problema es que mi aplicación no funciona en este momento (por una serie de razones, incluida la línea de lectura) y encuentro que los subprocesos son muy difíciles de depurar, especialmente si están en tiempo de espera. – Kidburla

Respuesta

2

Finalmente encontré una solución para esto. La mayoría de las respuestas aquí se basan en hilos, pero como he especificado anteriormente, estoy buscando una solución que no requiera hilos. Sin embargo, mi base fue el proceso. Lo que encontré fue que los procesos parecen salir si tanto la salida (llamada "entrada") como las secuencias de error están vacías y cerradas. Esto tiene sentido si lo piensas.

Así que solo sondeé los flujos de salida y error, y también intenté determinar si el proceso había finalizado o no. A continuación hay una copia aproximada de mi solución.

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

Sospecho que esto aún puede bloquearse cuando Reader # ready() indique la disponibilidad de un solo carácter (o más). También sospecho que podría haber casos en que todos los lectores denuncien falso en ready(), pero el conducto del proceso tiene datos para leer con una llamada al sistema potencialmente de bloqueo read(). –

+0

Sé que esto está respondido. Pero ya que me quedé atrapado con exactamente esta situación DOS VECES. Una pista: debe esperar antes de intentar leer de un proceso que se está ejecutando. es posible que solo esté comenzando a leer cuando el proceso aún se haya ejecutado por completo, lo que significa que ready() o readLine() pueden bloquear indefendiblemente ... – rubmz

0

¿Ha confirmado por experimento su afirmación de que ready() devolverá falso incluso si la secuencia subyacente está al final del archivo? Porque no esperaría que esa afirmación fuera correcta (aunque no he hecho el experimento).

+0

Desafortunadamente es correcto. Es uno de los mayores "errores" en Java IO, y me pone triste :( – ZoFreX

0

Puede usar InputStream.available() para ver si hay un nuevo resultado del proceso. Esto debería funcionar de la manera que desee si el proceso genera solo líneas completas, pero no es realmente confiable.

Un enfoque más confiable para el problema sería tener un hilo separado dedicado a leer el proceso y empujar cada línea que lee a alguna cola o consumidor.

+0

Hace InputStream.available() distingue entre los casos (2) y (3) en mi publicación original, ¿es mejor que ready()? Si es así, ¿por qué dices que no es confiable? – Kidburla

+0

Reader.ready() utiliza finalmente InputStream.available() para determinar si hay datos disponibles. Entonces no es más ni menos confiable. El problema es que no está garantizado que InputStream.available() informe cualquier dato disponible incluso cuando haya alguno. Esto se debe a que la transmisión podría no ser capaz de descubrir si hay algo disponible sin bloquear. E incluso si informa algo, no puede estar seguro de que terminará con un salto de línea. Si no termina con un salto de línea, todavía se bloqueará la invocación de readLine en ready() == verdadero. Yo recomendaría usar un hilo dedicado. – x4u

+0

Gracias, me parece que available() no distingue entre los casos (2) y (3) ninguno mejor que ready(), por lo que probablemente no resuelva mi problema. – Kidburla

1

Este es un problema bastante fundamental con la API de E/S de bloqueo de java.

sospecho que va a querer elegir uno de:

(1) Re-visitar la idea de utilizar el roscado. Esto no tiene que ser complicado, se hace correctamente, y sería dejar que su código de escape de un bloqueé/S leído bastante gracia, por ejemplo:

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) Utilice java.nio en lugar de java.io. Esta es una API más complicada, pero tiene una semántica no bloqueante.

2

Hay un estado donde algunos datos pueden estar en el búfer, pero no necesariamente suficientes para llenar una línea. En este caso, ready() devolvería true, pero se bloquearía llamando a readLine().

Debe ser capaz de construir fácilmente sus propios métodos ready() y readLine(). Su ready() realmente intentaría construir una línea, y solo cuando lo haya hecho con éxito devolvería true. Entonces su readLine() podría devolver la línea completamente formada.

+0

Creo que este es el mejor método a menos que se necesite un rendimiento adicional de almacenamiento en búfer. – ZoFreX

+0

¿Cómo es esto diferente de su solución a través de ZoFrex? Parece que mi lista personalizada() debería llamar a algún nivel de bytes listo() debajo, lo que no podría garantizar si read() bloquearía o no. Todavía abre mi aplicación hasta un posible bloqueo si el proceso subyacente se bloquea. – Kidburla

+0

Al examinar el código fuente de Java, encontré que BufferedReader # ready() es necesariamente conservador ya que comprueba el propio buffer y el lector subyacente como InputStreamReader. http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/io/BufferedReader.java Supongo que el último InputStreamReader informará también de su estado de ready() de forma conservadora . –

0

En general, debe implementar esto con varios hilos. Hay casos especiales, como leer desde un socket, donde el flujo subyacente tiene un recurso de tiempo de espera incorporado.

Sin embargo, no debería ser terriblemente complicado hacer esto con múltiples hilos. Este es un patrón que utilizo:

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

Se podría hacer su propia envoltura alrededor de InputStream o InputStreamReader que funciona en un nivel de byte a byte, para lo cual ready() devuelve valores exactos.

Sus otras opciones están enhebrando, lo que podría hacerse de manera simple (consulte algunas de las estructuras de datos simultáneas que ofrece Java) y NIO, que es muy complejo y probablemente excesivo.

+0

Esto suena como mi mejor opción (de manera similar con la respuesta de Lavinio), pero cuando dice "listo() devuelve valores precisos", ¿cómo estaría listo() distinguir entre mis casos (2) y (3)? – Kidburla

+0

Incidentalmente, InputStream no admite el método ready(), y InputStreamReader lo admite, pero el javadoc aún dice "Tenga en cuenta que devolver falso no garantiza que la siguiente lectura se bloqueará". – Kidburla

+0

Si devuelve verdadero, sin embargo, garantiza que la próxima lectura no se bloqueará. Creo. No sé cómo distinguir entre los casos 2 y 3, ¡y solo puedo sugerir probarlo! – ZoFreX

0

Si solo desea el tiempo de espera, los otros métodos aquí son posiblemente mejores. Si quieres un lector tamponada sin bloqueo, así es como yo lo haría, con hilos: (tenga en cuenta que no he probado esto y por lo menos se necesita un poco de manejo de excepciones añadido)

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

es como Esta utilizarlo:

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 

Hay cuatro estados posibles:

  1. conexión está abierta, no hay datos disponibles
  2. conexión está abierta, los datos son
  3. disponibles
  4. La conexión está cerrada, se dispone de datos
  5. La conexión está cerrada, no hay datos disponibles

Se puede distinguir entre ellas con el isClosed() y() métodos listos.

Cuestiones relacionadas