c# stream hang redirectstandardoutput

c# - StandardOutput.ReadToEnd() cuelga



stream hang (9)

Esta pregunta ya tiene una respuesta aquí:

Tengo un programa que utiliza con frecuencia un programa externo y lee sus resultados. Funciona bastante bien con la salida de redireccionamiento del proceso habitual, pero un argumento específico por alguna razón se cuelga cuando intento leerlo, no aparece ningún mensaje de error, no hay excepción, simplemente ''se detiene'' cuando llega a esa línea. Por supuesto, uso una función centralizada para llamar y leer el resultado del programa, que es el siguiente:

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 línea que se cuelga es result = proc.StandardOutput.ReadToEnd(); , pero nuevamente, no siempre, solo cuando se envía un argumento específico ("start-server"). Todos los demás argumentos funcionan bien, lee el valor y lo devuelve. También es extraño cómo se cuelga. No se congela ni da un error ni nada, solo detiene el procesamiento. Como si fuera un comando de ''retorno'', excepto que ni siquiera regresa a la función de llamada, simplemente detiene todo con la interfaz todavía en funcionamiento. Alguien experimentado esto antes? ¿Alguien tiene alguna idea de lo que debería probar? Supongo que es algo inesperado dentro de la transmisión en sí, pero ¿hay alguna manera de manejarlo / ignorarlo para que lo lea de todos modos?


¿Qué tal algo así como:

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


Algo que es elegante y funcionó para mí es:

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

Esta respuesta la encontré here y el truco está en usar Flush() y Close() en la entrada estándar.


El problema es que está utilizando los métodos ReadToEnd sincrónicos en las ReadToEnd StandardOutput y StandardError . Esto puede conducir a un punto muerto potencial que está experimentando. Esto incluso se describe en MSDN . La solución se describe allí. Básicamente, lo es: utilice la versión asíncrona BeginOutputReadLine para leer los datos de la transmisión StandardOutput :

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

Implementación de la lectura Async utilizando BeginOutputReadLine ver en ProcessStartInfo colgando en "WaitForExit"? ¿Por qué?


En caso de que alguien se tropiece con esta pregunta mientras usa Windows Forms y TextBox (o RichTextBox ) para mostrar los errores y resultados, el proceso regresa en tiempo real (tal como están escritos para procesar process.StandardOutput process.StandardError process.StandardOutput / process.StandardError ).

Necesita usar OutputDataReceived() / ErrorDataReceived() para leer ambas transmisiones sin interbloqueos, no hay manera (hasta donde yo sé) de evitar los bloqueos, de lo contrario, incluso la respuesta de Fedor, que ahora contiene la etiqueta "Answer" y la la mayoría de los "Me gusta" actualizados, no me funciona.

Sin embargo, cuando utiliza RichTextBox (o TextBox) para generar los datos, otro problema que encuentra es cómo escribir realmente los datos en el cuadro de texto en tiempo real (una vez que llega). Recibe el acceso a los datos dentro de uno de los hilos de fondo OutputDataReceived() / ErrorDataReceived() y solo puede AppendText() desde el hilo principal.

Lo primero que intenté hacer fue llamar al process.Start() desde un hilo de fondo y BeginInvoke() => AppendText() a BeginInvoke() => AppendText() en los OutputDataReceived() / ErrorDataReceived() mientras que el hilo principal era process.WaitForExit() .

Sin embargo, esto condujo a que mi forma se congelara y finalmente quedara suspendida por la eternidad. Después de unos días de intentarlo, terminé con la siguiente solución, que parece funcionar bastante bien.

En OutputDataReceived() , debe agregar los mensajes en una colección simultánea dentro de los OutputDataReceived() / ErrorDataReceived() mientras que el subproceso principal debe intentar constantemente extraer mensajes de esa colección y anexarlos al cuadro de texto:

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<string> messages = new ConcurrentQueue<string>(); 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();

El único inconveniente de este enfoque es el hecho de que puede perder mensajes en un caso bastante raro, cuando el proceso comienza a escribirlos entre process.Start() y process.BeginErrorReadLine() / process.BeginOutputReadLine() , solo téngalo en cuenta. La única forma de evitar eso es leer las transmisiones completas y (o) obtener acceso a ellas solo cuando el proceso finaliza.


La solución de la respuesta aceptada no funcionó para mí. Tuve que usar tareas para evitar el punto muerto:

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

Con una función GetStreamOutput siguiente manera:

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


Las soluciones propuestas con BeginOutputReadLine() son una buena manera, pero en situaciones como esa, no es aplicable, porque el proceso (ciertamente con el uso de WaitForExit() ) finaliza antes que la salida asíncrona terminada por completo.

Entonces, traté de implementarlo sincrónicamente y encontré que la solución está en usar el método Peek() de la clase StreamReader . Agregué el control de Peek() > -1 para asegurarme de que no es el final de la transmisión como se describe en el artículo de MSDN y finalmente funciona y deja de colgarse.

Aquí está el código:

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<string>(); while (process.StandardOutput.Peek() > -1) { output.Add(process.StandardOutput.ReadLine()); } while (process.StandardError.Peek() > -1) { output.Add(process.StandardError.ReadLine()); } process.WaitForExit();


Tuve el mismo problema de estancamiento. Este fragmento de código funcionó para mí.

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


Tuve el mismo problema que el error.

En base a su respuesta a Daniel Hilgarth, ni siquiera intenté usar esos códigos, aunque creo que hubieran funcionado para mí.

Como todavía quiero poder hacer una producción más elegante, finalmente decidí que lo haría con las dos salidas en un hilo de fondo.

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<object, CommandOutput> _outputs; private static Dictionary<object, CommandOutput> Outputs { get { if (_outputs != null) return _outputs; lock (OutputsLockObject) { _outputs = new Dictionary<object, CommandOutput>(); } 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 = ""; } }

Esto funcionó para mí y me permitió no tener que usar un tiempo de espera para la lectura.


primero

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

segundo

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

Esto es de MSDN