StandardOutput.ReadToEnd () si blocca

Ho un programma che usa spesso un programma esterno e legge i suoi output. Funziona piuttosto bene usando il solito output di reindirizzamento dei processi, ma per un motivo si blocca per un motivo specifico quando provo a leggerlo, nessun messaggio di errore – nessuna eccezione, si ferma solo quando raggiunge quella linea. Naturalmente uso una funzione centralizzata per chiamare e leggere l’output dal programma, che è questo:

public string ADBShell(string adbInput) { try { //Create Empty values string result = string.Empty; string error = string.Empty; string output = string.Empty; System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo(toolPath + "adb.exe"); procStartInfo.Arguments = adbInput; procStartInfo.RedirectStandardOutput = true; procStartInfo.RedirectStandardError = true; procStartInfo.UseShellExecute = false; procStartInfo.CreateNoWindow = true; procStartInfo.WorkingDirectory = toolPath; System.Diagnostics.Process proc = new System.Diagnostics.Process(); proc.StartInfo = procStartInfo; proc.Start(); // Get the output into a string proc.WaitForExit(); result = proc.StandardOutput.ReadToEnd(); error = proc.StandardError.ReadToEnd(); //Some ADB outputs use this if (result.Length > 1) { output += result; } if (error.Length > 1) { output += error; } Return output; } catch (Exception objException) { throw objException; } } 

La riga che si blocca è result = proc.StandardOutput.ReadToEnd(); , ma ancora una volta, non ogni volta, solo quando viene inviato un argomento specifico (“start-server”). Tutti gli altri argomenti funzionano bene – legge il valore e lo restituisce. È anche strano il modo in cui pende. Non congela né dà un errore o qualcosa, smette di processare. Come se fosse un comando ‘return’, tranne che non ritorna nemmeno alla funzione di chiamata, blocca tutto con l’interfaccia ancora triggers e funzionante. Qualcuno ha provato questo prima? Qualcuno ha idea di cosa dovrei provare? Suppongo che sia qualcosa di inaspettato all’interno del stream stesso, ma c’è un modo in cui posso gestire / ignorare questo in modo che lo legga comunque?

Le soluzioni proposte con BeginOutputReadLine() sono un buon modo, ma in situazioni del genere, non è applicabile, perché il processo (certamente con l’utilizzo di WaitForExit() ) esce prima che l’output asincrono sia terminato completamente.

Così, ho cercato di implementarlo in modo sincrono e ho scoperto che la soluzione è nell’uso del metodo Peek() dalla class StreamReader . Ho aggiunto il controllo per Peek() > -1 per assicurarmi che non sia la fine del stream come descritto nell’articolo di MSDN e che alla fine funzioni e smetta di appendere!

Ecco il codice:

 var process = new Process(); process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.WorkingDirectory = @"C:\test\"; process.StartInfo.FileName = "test.exe"; process.StartInfo.Arguments = "your arguments here"; process.Start(); var output = new List(); while (process.StandardOutput.Peek() > -1) { output.Add(process.StandardOutput.ReadLine()); } while (process.StandardError.Peek() > -1) { output.Add(process.StandardError.ReadLine()); } process.WaitForExit(); 

Il problema è che si stanno utilizzando i metodi ReadToEnd sincroni sia negli stream StandardError che StandardError . Questo può portare a un potenziale stallo che stai vivendo. Questo è anche descritto nel MSDN . La soluzione è descritta qui. In sostanza, è: utilizzare la versione asincrona BeginOutputReadLine per leggere i dati del stream StandardOutput :

 p.BeginOutputReadLine(); string error = p.StandardError.ReadToEnd(); p.WaitForExit(); 

Implementazione della lettura asincrona usando BeginOutputReadLine vedi in ProcessStartInfo sospeso su “WaitForExit”? Perché?

Che dire di qualcosa come:

 process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.OutputDataReceived += (sender, args) => { var outputData = args.Data; // ... }; process.ErrorDataReceived += (sender, args) => { var errorData = args.Data; // ... }; process.WaitForExit(); 

Ho avuto lo stesso problema di stallo. Questo frammento di codice ha funzionato per me.

  ProcessStartInfo startInfo = new ProcessStartInfo("cmd") { WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, CreateNoWindow = true }; Process process = new Process(); process.StartInfo = startInfo; process.Start(); process.StandardInput.WriteLine("echo hi"); process.StandardInput.WriteLine("exit"); var output = process.StandardOutput.ReadToEnd(); process.Dispose(); 

Qualcosa che è elegante e ha funzionato per me è:

 Process nslookup = new Process() { StartInfo = new ProcessStartInfo("nslookup") { RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden } }; nslookup.Start(); nslookup.StandardInput.WriteLine("set type=srv"); nslookup.StandardInput.WriteLine("_ldap._tcp.domain.local"); nslookup.StandardInput.Flush(); nslookup.StandardInput.Close(); string output = nslookup.StandardOutput.ReadToEnd(); nslookup.WaitForExit(); nslookup.Close(); 

Questa risposta l’ho trovata qui e il trucco sta usando Flush() e Close() sull’input standard.

Ho avuto lo stesso tipo di problema che l’errore era appena sospeso.

Sulla base della tua risposta a Daniel Hilgarth, non ho nemmeno provato a usare quei codici, anche se penso che avrebbero funzionato per me.

Dal momento che voglio essere in grado di fare un po ‘di output più fantasioso, alla fine ho deciso che lo avrei fatto con entrambe le uscite eseguite in un thread in background.

 public static class RunCommands { #region Outputs Property private static object _outputsLockObject; private static object OutputsLockObject { get { if (_outputsLockObject == null) Interlocked.CompareExchange(ref _outputsLockObject, new object(), null); return _outputsLockObject; } } private static Dictionary _outputs; private static Dictionary Outputs { get { if (_outputs != null) return _outputs; lock (OutputsLockObject) { _outputs = new Dictionary(); } return _outputs; } } #endregion public static string GetCommandOutputSimple(ProcessStartInfo info, bool returnErrorIfPopulated = true) { // Redirect the output stream of the child process. info.UseShellExecute = false; info.CreateNoWindow = true; info.RedirectStandardOutput = true; info.RedirectStandardError = true; var process = new Process(); process.StartInfo = info; process.ErrorDataReceived += ErrorDataHandler; process.OutputDataReceived += OutputDataHandler; var output = new CommandOutput(); Outputs.Add(process, output); process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); // Wait for the process to finish reading from error and output before it is finished process.WaitForExit(); Outputs.Remove(process); if (returnErrorIfPopulated && (!String.IsNullOrWhiteSpace(output.Error))) { return output.Error.TrimEnd('\n'); } return output.Output.TrimEnd('\n'); } private static void ErrorDataHandler(object sendingProcess, DataReceivedEventArgs errLine) { if (errLine.Data == null) return; if (!Outputs.ContainsKey(sendingProcess)) return; var commandOutput = Outputs[sendingProcess]; commandOutput.Error = commandOutput.Error + errLine.Data + "\n"; } private static void OutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine) { if (outputLine.Data == null) return; if (!Outputs.ContainsKey(sendingProcess)) return; var commandOutput = Outputs[sendingProcess]; commandOutput.Output = commandOutput.Output + outputLine.Data + "\n"; } } public class CommandOutput { public string Error { get; set; } public string Output { get; set; } public CommandOutput() { Error = ""; Output = ""; } } 

Questo ha funzionato per me e mi ha permesso di non dover usare un timeout per la lettura.

La soluzione della risposta accettata non ha funzionato per me. Ho dovuto usare i compiti per evitare il deadlock:

 //Code to start process here String outputResult = GetStreamOutput(process.StandardOutput); String errorResult = GetStreamOutput(process.StandardError); process.WaitForExit(); 

Con una funzione GetStreamOutput come segue:

 private string GetStreamOutput(StreamReader stream) { //Read output in separate task to avoid deadlocks var outputReadTask = Task.Run(() => stream.ReadToEnd()); return outputReadTask.Result; } 

Nel caso in cui qualcuno si imbattesse in questa domanda mentre wiling usasse Windows Forms e TextBox (o RichTextBox ) per mostrare gli errori e gli output i ritorni del processo in tempo reale (come vengono scritti per process.StandardOutput / process.StandardError ).

È necessario utilizzare OutputDataReceived() / ErrorDataReceived() per leggere entrambi i flussi senza deadlock, non c’è modo (per quanto ne so) di evitare deadlock altrimenti, anche la risposta di Fedor, che ora contiene il tag “Answer” e il più mi piace aggiornato, non fa il trucco per me.

Tuttavia, quando si utilizza il RichTextBox (o TextBox) per emettere i dati, un altro problema che si verifica è come scrivere effettivamente i dati nella casella di testo in tempo reale (una volta arrivato). Si riceve l’accesso ai dati all’interno di uno dei thread in background OutputDataReceived() / ErrorDataReceived() e si può solo AppendText() dal thread principale.

Quello che ho provato prima è stato chiamare process.Start() da un thread in background e quindi chiamare BeginInvoke() => AppendText() nei OutputDataReceived() / ErrorDataReceived() mentre il thread principale era process.WaitForExit() .

Tuttavia, questo ha portato alla mia forma di congelamento e in definitiva appeso per l’eternità. Dopo alcuni giorni di tentativi, ho trovato la soluzione in basso, che sembra funzionare piuttosto bene.

In breve, è necessario aggiungere i messaggi in una raccolta simultanea all’interno dei OutputDataReceived() / ErrorDataReceived() mentre il thread principale deve costantemente cercare di estrarre i messaggi da quella raccolta e aggiungerli nella casella di testo:

  ProcessStartInfo startInfo = new ProcessStartInfo(File, mysqldumpCommand); process.StartInfo.FileName = File; process.StartInfo.Arguments = mysqldumpCommand; process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.StartInfo.RedirectStandardInput = false; process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.StandardErrorEncoding = Encoding.UTF8; process.StartInfo.StandardOutputEncoding = Encoding.UTF8; process.EnableRaisingEvents = true; ConcurrentQueue messages = new ConcurrentQueue(); process.ErrorDataReceived += (object se, DataReceivedEventArgs ar) => { string data = ar.Data; if (!string.IsNullOrWhiteSpace(data)) messages.Enqueue(data); }; process.OutputDataReceived += (object se, DataReceivedEventArgs ar) => { string data = ar.Data; if (!string.IsNullOrWhiteSpace(data)) messages.Enqueue(data); }; process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); while (!process.HasExited) { string data = null; if (messages.TryDequeue(out data)) UpdateOutputText(data, tbOutput); Thread.Sleep(5); } process.WaitForExit(); 

L’unico svantaggio di questo approccio è il fatto che puoi perdere i messaggi in un caso piuttosto raro, quando il processo inizia a scriverli tra process.Start() e process.BeginErrorReadLine() / process.BeginOutputReadLine() , tieni questo a mente. L’unico modo per evitarlo è leggere gli stream completi e (o) ottenere l’accesso ad essi solo al termine del processo.

primo

  // Start the child process. Process p = new Process(); // Redirect the output stream of the child process. p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.FileName = "Write500Lines.exe"; p.Start(); // Do not wait for the child process to exit before // reading to the end of its redirected stream. // p.WaitForExit(); // Read the output stream first and then wait. string output = p.StandardOutput.ReadToEnd(); p.WaitForExit(); 

secondo

  // Do not perform a synchronous read to the end of both // redirected streams. // string output = p.StandardOutput.ReadToEnd(); // string error = p.StandardError.ReadToEnd(); // p.WaitForExit(); // Use asynchronous read operations on at least one of the streams. p.BeginOutputReadLine(); string error = p.StandardError.ReadToEnd(); p.WaitForExit(); 

Questo è da MSDN