2010-01-23 18 views
7

Ho un BufferedReader (generato da new BufferedReader(new InputStreamReader(process.getInputStream()))). Sono abbastanza nuovo al concetto di BufferedReader ma, a mio avviso, ha tre stati:Come determinare lo stato esatto di BufferedReader?

  1. Una riga è in attesa di essere letta; chiamare bufferedReader.readLine restituirà immediatamente questa stringa.
  2. Il flusso è aperto, ma non c'è nessuna riga in attesa di essere letta; chiamando bufferedReader.readLine si bloccherà il thread fino a quando una linea diventa disponibile.
  3. Il flusso è chiuso; chiamando bufferedReader.readLine restituirà null.

Ora voglio determinare lo stato di BufferedReader, in modo da poter determinare se posso leggerlo senza appendere la mia applicazione. Il processo sottostante (vedi sopra) è notoriamente inaffidabile e potrebbe quindi essersi bloccato; in questo caso, non voglio che la mia applicazione host si blocchi. Quindi sto implementando una specie di timeout. Ho provato a farlo prima con il threading ma è diventato orribilmente complicato.

La chiamata BufferedReader.ready() non consente di distinguere tra i casi (2) e (3) sopra. In altre parole, se ready() restituisce false, potrebbe essere che lo stream sia appena stato chiuso (in altre parole, il processo sottostante è stato chiuso in modo corretto) o potrebbe essersi verificato che il processo sottostante fosse bloccato.

Quindi la mia domanda è: come determinare quale di questi tre stati è il mio BufferedReader senza chiamare effettivamente readLine? Sfortunatamente non posso semplicemente chiamare readLine per controllare questo, in quanto apre la mia app fino a un blocco.

Sto usando JDK versione 1.5.

+0

A proposito, suppongo per il momento che il mio processo sottostante non possa pendere nel mezzo di una linea ... anche se potrebbe essere un'ipotesi errata, non è accaduto finora durante i test e ho avuto altri problemi !! – Kidburla

+0

N.B. Attualmente sto cercando altre opzioni rispetto al threading. Il problema era che la mia app non funziona al momento (per una serie di motivi incluso readLine) e trovo che i thread sono molto difficili da eseguire il debug, specialmente se sono in timeout. – Kidburla

risposta

2

Finalmente ho trovato una soluzione a questo. La maggior parte delle risposte qui si basano su thread, ma come ho specificato in precedenza, sto cercando una soluzione che non richiede thread. Tuttavia, la mia base era il processo. Quello che ho trovato è che i processi sembrano uscire se sia l'output (chiamato "input") che i flussi di errore sono vuoti e chiusi. Questo ha senso se ci pensi.

Quindi ho appena interrogato i flussi di output e di errore e ho anche cercato di determinare se il processo era uscito o meno. Di seguito è riportata una copia approssimativa della mia soluzione.

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

Sospetto che questo possa ancora bloccarsi perché Reader # ready() indica la disponibilità di un singolo carattere (o più). Sospetto anche che ci possano essere casi in cui tutti i lettori segnalano false in ready() ma la pipe del processo contiene dati da leggere con una chiamata di sistema read() potenzialmente bloccante. –

+0

So che questa è una risposta. Ma dal momento che sono rimasto bloccato esattamente con questa situazione DUE VOLTE. Un suggerimento: è necessario fare un'attesa prima di provare a leggere da un processo in corso. potresti appena iniziare a leggere quando il processo è stato ancora completamente eseguito, il che significa che ready() o readLine() potrebbero bloccare indefenately ... – rubmz

0

Hai confermato per sperimentazione la tua asserzione che ready() restituirà false anche se il flusso sottostante è alla fine del file? Perché non mi aspetterei che tale asserzione sia corretta (anche se non ho fatto l'esperimento).

+0

È sfortunatamente corretto. È uno dei più grandi "trucchi" in Java IO e mi rende triste :( – ZoFreX

0

È possibile utilizzare InputStream.available() per verificare se è presente un nuovo output dal processo. Questo dovrebbe funzionare nel modo desiderato se il processo produce solo linee complete, ma non è realmente affidabile.

Un approccio più affidabile al problema sarebbe quello di avere un thread separato dedicato alla lettura dal processo e che spinge ogni riga che legge in qualche coda o consumatore.

+0

Does InputStream.disponibile() distinguere tra casi (2) e (3) nel mio post originale meglio di ready()? Se è così, perché dici che non è affidabile? – Kidburla

+0

Reader.ready() utilizza infine InputStream.available() per determinare se sono disponibili dati. Quindi non è né più né meno affidabile. Il problema è che non è garantito che InputStream.available() riporti tutti i dati disponibili anche quando ce ne sono alcuni. Questo perché lo stream potrebbe non essere in grado di scoprire se c'è qualcosa disponibile senza bloccare. E anche se riporta qualcosa, non puoi essere sicuro che finirà con un avanzamento di riga. Se non termina con un avanzamento riga, chiamare readLine su ready() == true bloccherà comunque. Consiglierei di usare un thread dedicato. – x4u

+0

Grazie, mi sembra che available() non distingua tra casi (2) e (3) meglio di ready(), quindi questo probabilmente non risolve il mio problema. – Kidburla

1

Questo è un problema piuttosto fondamentale con l'API I/O di blocco di java.

ho il sospetto che si sta andando a voler scegliere uno dei:

(1) Ri-visitare l'idea di utilizzare threading. Questo non deve essere complicato, fatto correttamente, e sarebbe lasciate che il vostro codice di sfuggire a un I bloccato/O letto abbastanza con garbo, per esempio:

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) Utilizzare java.nio invece di java.io. Questa è un'API più complicata, ma ha una semantica non bloccante.

2

C'è uno stato in cui alcuni dati possono essere nel buffer, ma non necessariamente sufficienti per riempire una linea. In questo caso, ready() restituirebbe true, ma chiamando readLine() si bloccherebbe.

Si dovrebbe facilmente essere in grado di costruire i propri metodi ready() e readLine(). Il tuo ready() proverebbe effettivamente a costruire una linea, e solo quando lo ha fatto con successo restituirà true. Quindi il tuo readLine() potrebbe restituire la linea completa.

+0

Credo che questo sia il metodo migliore a meno che non sia necessaria la prestazione aggiuntiva del buffering. – ZoFreX

+0

In che modo è diverso dalla soluzione con ZoFrex? Sembra che il mio custom ready() dovrebbe chiamare sotto qualche byte-level ready(), che non può garantire se read() potrebbe bloccare o meno. Ancora apre la mia app fino a un possibile blocco se il processo sottostante si blocca. – Kidburla

+0

Guardando oltre il codice sorgente Java, ho scoperto che BufferedReader # ready() è necessariamente conservativo poiché controlla il proprio buffer e il lettore sottostante come InputStreamReader. http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/io/BufferedReader.java Immagino che quest'ultimo InputStreamReader riporterà il suo stato ready() anche in modo conservativo . –

0

In generale, è necessario implementare questo con più thread. Esistono casi speciali, come la lettura da un socket, in cui lo stream sottostante ha una funzione di timeout integrata.

Tuttavia, non dovrebbe essere terribilmente complicato farlo con più thread. Questo è un modello che uso:

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

Si potrebbe rendere il proprio involucro intorno InputStream o InputStreamReader che funziona su un livello di byte per byte, per il quale pronto() restituisce i valori precisi.

Le altre opzioni sono threading che potrebbe essere fatto semplicemente (esaminare alcune delle strutture di dati concorrenti offerte Java) e NIO, che è molto complesso e probabilmente eccessivo.

+0

Questa sembra la mia opzione migliore (analogamente alla risposta di lavinio), ma quando si dice "ready() restituisce valori precisi", come sarebbe pronto() distinguere tra i miei casi (2) e (3)? – Kidburla

+0

Incidentalmente, InputStream non supporta il metodo ready(), e InputStreamReader lo supporta ma javadoc dice ancora "Notare che restituire false non garantisce che la prossima lettura bloccherà". – Kidburla

+0

Se restituisce true, tuttavia garantisce che la lettura successiva non verrà bloccata. Credo. Non so distinguere tra i casi 2 e 3 e posso solo suggerire di provarlo! – ZoFreX

0

Se si desidera solo il timeout, gli altri metodi qui sono probabilmente migliori. Se si desidera un lettore non bloccante tamponata, ecco come lo farei, con fili: (si prega di notare che non ho testato questo e per lo meno ha bisogno di un po 'di gestione delle eccezioni aggiunto)

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

Ecco come per usarlo:

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 

ci sono quattro stati possibili:

  1. connessione è aperta, non sono disponibili dati
  2. connessione è aperta, i dati sono disponibile
  3. connessione è chiusa, i dati sono disponibili
  4. connessione è chiusa, non sono disponibili dati

È possibile distinguere tra loro con l'isClosed() e metodi (pronto).

Problemi correlati