waitforexit visual start example ejemplos c# processstartinfo

c# - visual - ProcessStartInfo colgando en "WaitForExit"? ¿Por qué?



visual c# process start (18)

Tengo el siguiente código:

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args)); info.CreateNoWindow = true; info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; info.RedirectStandardOutput = true; info.UseShellExecute = false; System.Diagnostics.Process p = System.Diagnostics.Process.Start(info); p.WaitForExit(); Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

Sé que el resultado del proceso que estoy comenzando es de alrededor de 7 MB de longitud. Ejecutarlo en la consola de Windows funciona bien. Lamentablemente, programáticamente, esto se bloquea indefinidamente en WaitForExit. Tenga en cuenta también que este código NO se cuelga para salidas más pequeñas (como 3KB).

¿Es posible que el StandardOutput interno en ProcessStartInfo no pueda almacenar en buffer 7MB? Si es así, ¿qué debería hacer? Si no, ¿qué estoy haciendo mal?


Introducción

La respuesta aceptada actualmente no funciona (lanza una excepción) y hay demasiadas soluciones temporales pero no un código completo. Esto obviamente está perdiendo el tiempo de muchas personas porque esta es una pregunta popular.

Combinando la respuesta de Mark Byers y la respuesta de Karol Tyl, escribí un código completo basado en cómo quiero usar el método Process.Start.

Uso

Lo he usado para crear un diálogo de progreso sobre los comandos de git. Así es como lo he usado:

private bool Run(string fullCommand) { Error = ""; int timeout = 5000; var result = ProcessNoBS.Start( filename: @"C:/Program Files/Git/cmd/git.exe", arguments: fullCommand, timeoutInMs: timeout, workingDir: @"C:/test"); if (result.hasTimedOut) { Error = String.Format("Timeout ({0} sec)", timeout/1000); return false; } if (result.ExitCode != 0) { Error = (String.IsNullOrWhiteSpace(result.stderr)) ? result.stdout : result.stderr; return false; } return true; }

En teoría, también puedes combinar stdout y stderr, pero no lo he probado.

Código

public struct ProcessResult { public string stdout; public string stderr; public bool hasTimedOut; private int? exitCode; public ProcessResult(bool hasTimedOut = true) { this.hasTimedOut = hasTimedOut; stdout = null; stderr = null; exitCode = null; } public int ExitCode { get { if (hasTimedOut) throw new InvalidOperationException( "There was no exit code - process has timed out."); return (int)exitCode; } set { exitCode = value; } } } public class ProcessNoBS { public static ProcessResult Start(string filename, string arguments, string workingDir = null, int timeoutInMs = 5000, bool combineStdoutAndStderr = false) { using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false)) using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false)) { using (var process = new Process()) { var info = new ProcessStartInfo(); info.CreateNoWindow = true; info.FileName = filename; info.Arguments = arguments; info.UseShellExecute = false; info.RedirectStandardOutput = true; info.RedirectStandardError = true; if (workingDir != null) info.WorkingDirectory = workingDir; process.StartInfo = info; StringBuilder stdout = new StringBuilder(); StringBuilder stderr = combineStdoutAndStderr ? stdout : new StringBuilder(); var result = new ProcessResult(); try { process.OutputDataReceived += (sender, e) => { if (e.Data == null) outputWaitHandle.Set(); else stdout.AppendLine(e.Data); }; process.ErrorDataReceived += (sender, e) => { if (e.Data == null) errorWaitHandle.Set(); else stderr.AppendLine(e.Data); }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); if (process.WaitForExit(timeoutInMs)) result.ExitCode = process.ExitCode; // else process has timed out // but that''s already default ProcessResult result.stdout = stdout.ToString(); if (combineStdoutAndStderr) result.stderr = null; else result.stderr = stderr.ToString(); return result; } finally { outputWaitHandle.WaitOne(timeoutInMs); errorWaitHandle.WaitOne(timeoutInMs); } } } } }


Crédito a EM0 por https://.com/a/17600012/4151626

Las otras soluciones (incluidas las EM0) todavía están bloqueadas para mi aplicación, debido a los tiempos de espera internos y al uso de StandardOutput y StandardError por la aplicación engendrada. Esto es lo que funcionó para mí:

Process p = new Process() { StartInfo = new ProcessStartInfo() { FileName = exe, Arguments = args, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true } }; p.Start(); string cv_error = null; Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); }); et.Start(); string cv_out = null; Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); }); ot.Start(); p.WaitForExit(); ot.Join(); et.Join();

Editar: inicialización agregada de StartInfo para codificar muestra


Creo que este es un enfoque simple y mejor (no necesitamos AutoResetEvent ):

public static string GGSCIShell(string Path, string Command) { using (Process process = new Process()) { process.StartInfo.WorkingDirectory = Path; process.StartInfo.FileName = Path + @"/ggsci.exe"; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardInput = true; process.StartInfo.UseShellExecute = false; StringBuilder output = new StringBuilder(); process.OutputDataReceived += (sender, e) => { if (e.Data != null) { output.AppendLine(e.Data); } }; process.Start(); process.StandardInput.WriteLine(Command); process.BeginOutputReadLine(); int timeoutParts = 10; int timeoutPart = (int)TIMEOUT / timeoutParts; do { Thread.Sleep(500);//sometimes halv scond is enough to empty output buff (therefore "exit" will be accepted without "timeoutPart" waiting) process.StandardInput.WriteLine("exit"); timeoutParts--; } while (!process.WaitForExit(timeoutPart) && timeoutParts > 0); if (timeoutParts <= 0) { output.AppendLine("------ GGSCIShell TIMEOUT: " + TIMEOUT + "ms ------"); } string result = output.ToString(); return result; } }


Después de leer todas las publicaciones aquí, me decidí por la solución consolidada de Marko Avlijaš. Sin embargo , no resolvió todos mis problemas.

En nuestro entorno, tenemos un servicio de Windows que está programado para ejecutar cientos de archivos .bat .cmd .exe, ... etc. diferentes que se han acumulado a lo largo de los años y que fueron escritos por muchas personas diferentes y en diferentes estilos. No tenemos control sobre la escritura de los programas y scripts, solo somos responsables de programar, ejecutar e informar sobre el éxito / fracaso.

Así que intenté casi todas las sugerencias aquí con diferentes niveles de éxito. La respuesta de Marko fue casi perfecta, pero cuando se ejecutaba como un servicio, no siempre capturaba la salida estándar. Nunca llegué al fondo de por qué no.

La única solución que encontramos que funciona en TODOS nuestros casos es esta: http://csharptest.net/319/using-the-processrunner-class/index.html


El problema con ObjectDisposedException no controlada ocurre cuando se agota el tiempo de espera del proceso. En tal caso, las otras partes de la condición:

if (process.WaitForExit(timeout) && outputWaitHandle.WaitOne(timeout) && errorWaitHandle.WaitOne(timeout))

no se ejecutan Resolví este problema de la siguiente manera:

using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false)) using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false)) { using (process = new Process()) { // preparing ProcessStartInfo try { process.OutputDataReceived += (sender, e) => { if (e.Data == null) { outputWaitHandle.Set(); } else { outputBuilder.AppendLine(e.Data); } }; process.ErrorDataReceived += (sender, e) => { if (e.Data == null) { errorWaitHandle.Set(); } else { outputBuilder.AppendLine(e.Data); } }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); if (process.WaitForExit(timeout)) { exitCode = process.ExitCode; } else { // timed out } output = outputBuilder.ToString(); } finally { outputWaitHandle.WaitOne(timeout); errorWaitHandle.WaitOne(timeout); } } }


El problema es que si redirige a StandardOutput y / o StandardError el búfer interno puede llenarse. Cualquier orden que use, puede haber un problema:

  • Si espera a que el proceso salga antes de leer StandardOutput el proceso puede bloquear la tentativa de intentar escribir en él, por lo que el proceso nunca termina.
  • Si lee desde StandardOutput usando ReadToEnd, su proceso puede bloquearse si el proceso nunca cierra StandardOutput (por ejemplo, si nunca termina, o si está bloqueado escribiendo en StandardError ).

La solución es usar lecturas asíncronas para garantizar que el búfer no se llene. Para evitar interbloqueos y recopilar todos los resultados de StandardOutput y StandardError , puede hacer esto:

EDITAR: Consulte las respuestas a continuación para saber cómo evitar una ObjectDisposedException si se produce el tiempo de espera.

using (Process process = new Process()) { process.StartInfo.FileName = filename; process.StartInfo.Arguments = arguments; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; StringBuilder output = new StringBuilder(); StringBuilder error = new StringBuilder(); using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false)) using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false)) { process.OutputDataReceived += (sender, e) => { if (e.Data == null) { outputWaitHandle.Set(); } else { output.AppendLine(e.Data); } }; process.ErrorDataReceived += (sender, e) => { if (e.Data == null) { errorWaitHandle.Set(); } else { error.AppendLine(e.Data); } }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); if (process.WaitForExit(timeout) && outputWaitHandle.WaitOne(timeout) && errorWaitHandle.WaitOne(timeout)) { // Process completed. Check process.ExitCode here. } else { // Timed out. } } }


Esta es una solución más moderna y agitable, Task Parallel Library (TPL) para .NET 4.5 y superior.

Ejemplo de uso

try { var exitCode = await StartProcess( "dotnet", "--version", @"C:/", 10000, Console.Out, Console.Out); Console.WriteLine($"Process Exited with Exit Code {exitCode}!"); } catch (TaskCanceledException) { Console.WriteLine("Process Timed Out!"); }

Implementación

public static async Task<int> StartProcess( string filename, string arguments, string workingDirectory= null, int? timeout = null, TextWriter outputTextWriter = null, TextWriter errorTextWriter = null) { using (var process = new Process() { StartInfo = new ProcessStartInfo() { CreateNoWindow = true, Arguments = arguments, FileName = filename, RedirectStandardOutput = outputTextWriter != null, RedirectStandardError = errorTextWriter != null, UseShellExecute = false, WorkingDirectory = workingDirectory } }) { process.Start(); var cancellationTokenSource = timeout.HasValue ? new CancellationTokenSource(timeout.Value) : new CancellationTokenSource(); var tasks = new List<Task>(3) { process.WaitForExitAsync(cancellationTokenSource.Token) }; if (outputTextWriter != null) { tasks.Add(ReadAsync( x => { process.OutputDataReceived += x; process.BeginOutputReadLine(); }, x => process.OutputDataReceived -= x, outputTextWriter, cancellationTokenSource.Token)); } if (errorTextWriter != null) { tasks.Add(ReadAsync( x => { process.ErrorDataReceived += x; process.BeginErrorReadLine(); }, x => process.ErrorDataReceived -= x, errorTextWriter, cancellationTokenSource.Token)); } await Task.WhenAll(tasks); return process.ExitCode; } } /// <summary> /// Waits asynchronously for the process to exit. /// </summary> /// <param name="process">The process to wait for cancellation.</param> /// <param name="cancellationToken">A cancellation token. If invoked, the task will return /// immediately as cancelled.</param> /// <returns>A Task representing waiting for the process to end.</returns> public static Task WaitForExitAsync( this Process process, CancellationToken cancellationToken = default(CancellationToken)) { process.EnableRaisingEvents = true; var taskCompletionSource = new TaskCompletionSource<object>(); EventHandler handler = null; handler = (sender, args) => { process.Exited -= handler; taskCompletionSource.TrySetResult(null); }; process.Exited += handler; if (cancellationToken != default(CancellationToken)) { cancellationToken.Register( () => { process.Exited -= handler; taskCompletionSource.TrySetCanceled(); }); } return taskCompletionSource.Task; } /// <summary> /// Reads the data from the specified data recieved event and writes it to the /// <paramref name="textWriter"/>. /// </summary> /// <param name="addHandler">Adds the event handler.</param> /// <param name="removeHandler">Removes the event handler.</param> /// <param name="textWriter">The text writer.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task representing the asynchronous operation.</returns> public static Task ReadAsync( this Action<DataReceivedEventHandler> addHandler, Action<DataReceivedEventHandler> removeHandler, TextWriter textWriter, CancellationToken cancellationToken = default(CancellationToken)) { var taskCompletionSource = new TaskCompletionSource<object>(); DataReceivedEventHandler handler = null; handler = new DataReceivedEventHandler( (sender, e) => { if (e.Data == null) { removeHandler(handler); taskCompletionSource.TrySetResult(null); } else { textWriter.WriteLine(e.Data); } }); addHandler(handler); if (cancellationToken != default(CancellationToken)) { cancellationToken.Register( () => { removeHandler(handler); taskCompletionSource.TrySetCanceled(); }); } return taskCompletionSource.Task; }


Esta publicación puede estar desactualizada, pero descubrí que la causa principal por la que generalmente cuelga es debido al desbordamiento de la pila para la salida redirectStandard o si tienes redirectStandarderror.

Como los datos de salida o los datos de error son grandes, causará un tiempo de suspensión ya que todavía está procesándose por una duración indefinida.

para resolver este problema:

p.StartInfo.RedirectStandardoutput = False p.StartInfo.RedirectStandarderror = False


Estaba teniendo el mismo problema, pero la razón era diferente. Sin embargo, sucedería en Windows 8, pero no en Windows 7. La siguiente línea parece haber causado el problema.

pProcess.StartInfo.UseShellExecute = False

La solución fue NO deshabilitar UseShellExecute. Ahora recibí una ventana emergente de Shell, que no es deseada, pero mucho mejor que el programa, sin esperar que ocurra nada en particular. Así que agregué la siguiente solución para eso:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

Ahora, lo único que me molesta es por qué sucede esto en Windows 8 en primer lugar.


La manual de Process.StandardOutput dice que debe leer antes de esperar, de lo contrario, puede interbloquear, fragmento copiado a continuación:

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


La respuesta de Mark Byers es excelente, pero solo agregaría lo siguiente: los delegados OutputDataReceived y ErrorDataReceived deben eliminarse antes de que se eliminen outputWaitHandle y errorWaitHandle. Si el proceso continúa generando datos después de que se ha excedido el tiempo de espera y luego finaliza, se accederá a las variables outputWaitHandle y errorWaitHandle después de que se eliminen.

(Para su información, tuve que agregar esta advertencia como respuesta, ya que no pude comentar su publicación).


Lo solucioné de esta manera:

Process proc = new Process(); proc.StartInfo.FileName = batchFile; proc.StartInfo.UseShellExecute = false; proc.StartInfo.CreateNoWindow = true; proc.StartInfo.RedirectStandardError = true; proc.StartInfo.RedirectStandardInput = true; proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; proc.Start(); StreamWriter streamWriter = proc.StandardInput; StreamReader outputReader = proc.StandardOutput; StreamReader errorReader = proc.StandardError; while (!outputReader.EndOfStream) { string text = outputReader.ReadLine(); streamWriter.WriteLine(text); } while (!errorReader.EndOfStream) { string text = errorReader.ReadLine(); streamWriter.WriteLine(text); } streamWriter.Close(); proc.WaitForExit();

Redirigí la entrada, la salida y el error, y manejé la lectura de los flujos de salida y error. Esta solución funciona para SDK 7- 8.1, tanto para Windows 7 como para Windows 8


Ninguna de las respuestas anteriores está haciendo el trabajo.

La solución Rob se bloquea y la solución ''Mark Byers'' obtiene la excepción eliminada. (Probé las "soluciones" de las otras respuestas).

Entonces decidí sugerir otra solución:

public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode) { string outputLocal = ""; int localExitCode = -1; var task = System.Threading.Tasks.Task.Factory.StartNew(() => { outputLocal = process.StandardOutput.ReadToEnd(); process.WaitForExit(); localExitCode = process.ExitCode; }, token); if (task.Wait(timeoutSec, token)) { output = outputLocal; exitCode = localExitCode; } else { exitCode = -1; output = ""; } } using (var process = new Process()) { process.StartInfo = ...; process.Start(); string outputUnicode; int exitCode; GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode); }

Este código se depuró y funciona perfectamente.


Rob respondió y me ahorró unas horas más de pruebas. Lea el buffer de salida / error antes de esperar:

// Read the output stream first and then wait. string output = p.StandardOutput.ReadToEnd(); p.WaitForExit();


Sé que esto ya es viejo pero, después de leer toda esta página, ninguna de las soluciones me funcionaba, aunque no probé a Muhammad Rehan porque el código era un poco difícil de seguir, aunque creo que estaba en el camino correcto. . Cuando digo que no funcionó no es del todo cierto, a veces funcionaría bien, supongo que tiene que ver con la longitud de la salida antes de una marca EOF.

De todos modos, la solución que funcionó para mí fue utilizar diferentes subprocesos para leer StandardOutput y StandardError y escribir los mensajes.

StreamWriter sw = null; var queue = new ConcurrentQueue<string>(); var flushTask = new System.Timers.Timer(50); flushTask.Elapsed += (s, e) => { while (!queue.IsEmpty) { string line = null; if (queue.TryDequeue(out line)) sw.WriteLine(line); } sw.FlushAsync(); }; flushTask.Start(); using (var process = new Process()) { try { process.StartInfo.FileName = @"..."; process.StartInfo.Arguments = $"..."; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.Start(); var outputRead = Task.Run(() => { while (!process.StandardOutput.EndOfStream) { queue.Enqueue(process.StandardOutput.ReadLine()); } }); var errorRead = Task.Run(() => { while (!process.StandardError.EndOfStream) { queue.Enqueue(process.StandardError.ReadLine()); } }); var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0); if (Task.WaitAll(new[] { outputRead, errorRead }, timeout) && process.WaitForExit((int)timeout.TotalMilliseconds)) { if (process.ExitCode != 0) { throw new Exception($"Failed run... blah blah"); } } else { throw new Exception($"process timed out after waiting {timeout}"); } } catch (Exception e) { throw new Exception($"Failed to succesfully run the process.....", e); } } }

Espero que esto ayude a alguien, ¡quien pensó que esto podría ser tan difícil!


Tenemos este problema también (o una variante).

Pruebe lo siguiente:

1) Agregue un tiempo de espera a p.WaitForExit (nnnn); donde nnnn está en milisegundos.

2) Ponga la llamada ReadToEnd antes de la llamada WaitForExit. Esto es lo que hemos visto recomendar a MS.


Traté de hacer una clase que resolvería tu problema utilizando la lectura de flujo asíncrono, teniendo en cuenta las respuestas de Mark Byers, Rob, stevejay. Al hacerlo, me di cuenta de que hay un error relacionado con la lectura de flujo de salida del proceso asíncrono.

Informé de ese error en Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134

Resumen:

No puedes hacer eso:

process.BeginOutputReadLine (); process.Start ();

Recibirá System.InvalidOperationException: StandardOut no se redirigió o el proceso aún no se inició.

=============================================== =============================================== ========================

Luego, debe iniciar la lectura de salida asíncrona después de iniciar el proceso:

process.Start (); process.BeginOutputReadLine ();

Si lo hace, realice una condición de carrera porque la secuencia de salida puede recibir datos antes de establecerlo como asíncrono:

process.Start(); // Here the operating system could give the cpu to another thread. // For example, the newly created thread (Process) and it could start writing to the output // immediately before next line would execute. // That create a race condition. process.BeginOutputReadLine();

=============================================== =============================================== ========================

Entonces, algunas personas podrían decir que solo tiene que leer la secuencia antes de configurarla como asincrónica. Pero el mismo problema ocurre. Habrá una condición de carrera entre la lectura sincrónica y establecerá la transmisión en modo asíncrono.

=============================================== =============================================== ========================

No hay forma de lograr una lectura asíncrona segura de una secuencia de salida de un proceso en la forma real en que se diseñó "Proceso" y "ProcessStartInfo".

Probablemente sea mejor usar lectura asincrónica como la sugerida por otros usuarios para su caso. Pero debe tener en cuenta que podría perder información debido a su condición de raza.


Una nota de precaución con la solución Mark Byers :