Alternativa a StreamReader.Peek e Thread.Interrupt

Prefazione veloce di ciò che sto cercando di fare. Voglio iniziare un processo e avviare due thread per monitorare lo stderr e lo stdin. Ogni thread mastica i bit del stream e poi lo invia a un NetworkStream. Se c’è un errore in entrambi i thread, entrambi i thread devono morire immediatamente.

Ognuno di questi processi con i thread di monitoraggio stdout e stdin vengono scorporati da un processo del server principale. La ragione per cui questo diventa complicato è perché possono facilmente essere 40 o 50 di questi processi in un dato momento. Solo durante le ripartenze di riavvio del mattino ci sono sempre più di 50 connessioni, ma in realtà deve essere in grado di gestire 100 o più. Ho provato con 100 connessioni simultanee.

try { StreamReader reader = this.myProcess.StandardOutput; char[] buffer = new char[4096]; byte[] data; int read; while (reader.Peek() > -1 ) // This can block before stream is streamed to { read = reader.Read(buffer, 0, 4096); data = Server.ClientEncoding.GetBytes(buffer, 0, read); this.clientStream.Write(data, 0, data.Length); //ClientStream is a NetworkStream } } catch (Exception err) { Utilities.ConsoleOut(string.Format("StdOut err for client {0} -- {1}", this.clientID, err)); this.ShutdownClient(true); } 

Questo blocco di codice viene eseguito in una discussione che ora non è Sfondo. Esiste un thread simile per lo stream StandardError. Sto usando questo metodo invece di ascoltare OutputDataReceived e ErrorDataReceived perché c’è stato un problema in Mono che ha causato il mancato avvio di questi eventi in modo corretto e anche se sembra essere risolto ora mi piace che questo metodo assicuri che sto leggendo e scrivendo tutto sequenziale.

ShutdownClient con True cerca semplicemente di uccidere entrambi i thread. Sfortunatamente l’unico modo che ho trovato per fare questo lavoro è usare un interrupt sugli oggetti stdErrThread e stdOutThread. Idealmente sbirciare non bloccherebbe e potrei semplicemente usare un evento di reset manuale per continuare a cercare nuovi dati su stdOut o stdIn e poi morire quando l’evento è capovolto.

Dubito che questo sia il modo migliore per farlo. C’è un modo per eseguire ciò senza utilizzare un interrupt?

Mi piacerebbe cambiare, perché ho appena visto nei miei registri che ho perso una ThreadInterruptException lanciata all’interno di Utlities.ConsoleOut. Questo fa solo un System.Console.Write se una variabile statica è vera, ma immagino che blocchi da qualche parte.

modifiche:

Questi thread fanno parte di un thread padre che viene avviato in massa da un server su richiesta. Pertanto non posso impostare i thread StdOut e StdErr sullo sfondo e uccidere l’applicazione. Potrei uccidere il thread principale dal server principale, ma questo di nuovo si appiccicerebbe al blocco di Peek.

Informazioni aggiunte su questo essendo un server.

Inoltre sto iniziando a realizzare un metodo di Queuing migliore per le query potrebbe essere la soluzione definitiva.

Posso dire che tutto questo casino deriva dal fatto che Peek blocca. Stai davvero provando a sistemare qualcosa che è fondamentalmente rotto nel framework e che non è mai facile (cioè non un hack sporco). Personalmente, correggo la radice del problema, che è il blocco Peek . Mono avrebbe seguito l’implementazione di Microsoft e quindi si ritroverebbe con lo stesso problema.

Mentre so esattamente come risolvere il problema se dovessi avere il permesso di modificare il codice sorgente del framework, la soluzione è lunga e lunga.

Ma qui va.

In sostanza, ciò che Microsoft deve fare è modificare Process.StartWithCreateProcess modo che standardOutput e standardError PipeStreamReader entrambi un tipo specializzato di StreamReader (ad esempio PipeStreamReader ).

In questo PipeStreamReader , hanno bisogno di sovrascrivere entrambi ReadBuffer overload di ReadBuffer (cioè è necessario prima modificare entrambi gli overload in virtual in StreamReader ) in modo tale che prima di una lettura, PeekNamedPipe venga chiamata per fare la vera sbirciatina. Al momento, FileStream.Read() (chiamato da Peek() ) bloccherà le letture di pipe quando non ci sono dati disponibili per la lettura. Mentre FileStream.Read() con 0 byte funziona bene sui file, non funziona molto bene sulle pipe. In effetti, il team di .NET ha perso una parte importante della documentazione del pipe: PeekNamedPipe WinAPI.

La funzione PeekNamedPipe è simile alla funzione ReadFile con le seguenti eccezioni:

La funzione ritorna sempre immediatamente in un’applicazione a thread singolo, anche se non ci sono dati nella pipe . La modalità di attesa di un handle di named pipe (bloccante o non bloccante) non ha alcun effetto sulla funzione.

La cosa migliore in questo momento senza questo problema risolto nel framework sarebbe quella di implementare la propria class Process (sarebbe sufficiente un involucro sottile attorno a WinAPI).

Perché non basta impostare entrambi i thread per tornare indietro e poi uccidere l’app? Provocherebbe una chiusura immediata di entrambi i fili.

Stai costruendo un server. Vuoi evitare il blocco. La soluzione ovvia è utilizzare le API asincrone:

 var myProcess = Process.GetCurrentProcess(); StreamReader reader = myProcess.StandardOutput; char[] buffer = new char[4096]; byte[] data; int read; while (!myProcess.HasExited) { read = await reader.ReadAsync(buffer, 0, 4096); data = Server.ClientEncoding.GetBytes(buffer, 0, read); await this.clientStream.WriteAsync(data, 0, data.Length); } 

Non c’è bisogno di sprecare discussioni facendo il lavoro di I / O 🙂

Sbarazzati della sbirciatina e usa il metodo qui sotto per leggere dai flussi di output del processo. ReadLine () restituisce null al termine del processo. Per unirti a questa discussione con il thread chiamante, attendi che il processo finisca o uccidi tu stesso il processo. ShutdownClient () dovrebbe semplicemente uccidere () il processo che farà sì che anche l’altro thread legga StdOut o StdErr per uscire.

  private void ReadToEnd() { string nextLine; while ((nextLine = stream.ReadLine()) != null) { output.WriteLine(nextLine); } }