Pregunta

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 la salida del proceso que estoy iniciando tiene alrededor de 7 MB de largo. Ejecutarlo en la consola de Windows funciona bien. Desafortunadamente programáticamente esto se cuelga indefinidamente en WaitForExit. Tenga en cuenta 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 7MB? Si es así, ¿qué debo hacer en su lugar? Si no, ¿qué estoy haciendo mal?

¿Fue útil?

Solución

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

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

La solución es usar lecturas asíncronas para garantizar que el búfer no se llene. Para evitar cualquier punto muerto 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 agota 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.
        }
    }
}

Otros consejos

La documentación para Process.StandardOutput dice que lea antes de esperar, de lo contrario puede bloquearse, el 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 yo solo agregaría lo siguiente: los delegados OutputDataReceived y ErrorDataReceived deben eliminarse antes de que se elimine la outputWaitHandle y el errorWaitHandle. Si el proceso continúa emitiendo datos después de que se haya excedido el tiempo de espera y luego finaliza, se accederá a las variables outputWaitHandle y errorWaitHandle después de eliminarlas.

(FYI tuve que agregar esta advertencia como respuesta, ya que no podía comentar sobre su publicación.)

El problema con la excepción ObjectDisposedException no manejada ocurre cuando se agota el tiempo 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 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
                    {
                        errorBuilder.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);
        }
    }
}

Esta es una solución basada en la biblioteca paralela de tareas (TPL, por sus siglas en inglés) más moderna para .NET 4.5 y superior.

Ejemplo de uso

try
{
    var exitCode = await StartProcess(
        "dotnet", 
        "--version", 
        @"C:\",
        10000, 
        Console.Out, 
        Console.Out);
    Console.WriteLine(
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;
}
quot;Process Exited with Exit Code {exitCode}!"); } catch (TaskCanceledException) { Console.WriteLine("Process Timed Out!"); }

Implementación

<*>

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

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

También tenemos este problema (o una variante).

Prueba 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 que MS recomienda.

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

Las otras soluciones (incluidas las EM0) aún 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 generada. 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: se agregó la inicialización de StartInfo a la muestra de código

Lo resolví 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 las secuencias de salida y error. Esta solución funciona para SDK 7- 8.1, tanto para Windows 7 como para Windows 8

Esta publicación puede estar desactualizada, pero descubrí que la causa principal por la que generalmente se bloquea se debe a un desbordamiento de pila para la redirectStandardoutput o si tiene redirectStandarderror.

Como los datos de salida o los datos de error son grandes, provocará un tiempo de bloqueo ya que todavía se está procesando por una duración indefinida.

para resolver este problema:

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

Traté de hacer una clase que resolviera su problema usando lectura de flujo asíncrona, teniendo en cuenta las respuestas de Mark Byers, Rob, stevejay. Al hacerlo, me di cuenta de que hay un error relacionado con el flujo de salida de proceso asíncrono leído.

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

Resumen:

  

No puedes hacer eso:

     

process.BeginOutputReadLine (); proceso.Inicio ();

     

Recibirá System.InvalidOperationException: StandardOut tiene   no ha sido redirigido o el proceso aún no ha comenzado.

     

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

     

Luego tienes que iniciar la lectura asíncrona de salida después de que el proceso es   comenzado:

     

proceso.Inicio (); process.BeginOutputReadLine ();

     

Al hacerlo, crea una condición de carrera porque la secuencia de salida puede recibir   datos antes de configurarlo en 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 tienes que leer la transmisión.   Antes de configurarlo en asíncrono. Pero ocurre el mismo problema. Ahí   será una condición de carrera entre la lectura síncrona y establecer la   transmitir en modo asíncrono.

     

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

     

No hay manera de lograr una lectura asíncrona segura de un flujo de salida   de un proceso en la forma real " Proceso " y " ProcessStartInfo " tiene   sido diseñado.

Probablemente sea mejor usar la lectura asíncrona, como lo sugieren otros usuarios para su caso. Pero debe tener en cuenta que podría perder alguna información debido a la condición de la raza.

Ninguna de las respuestas anteriores está haciendo el trabajo.

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

Así que 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.

Introducción

La respuesta actualmente aceptada no funciona (produce una excepción) y hay demasiadas soluciones alternativas pero no hay código completo. Obviamente, esto es una pérdida de tiempo para la gente porque esta es una pregunta popular.

Combinando la respuesta de Mark Byers y la respuesta de Karol Tyl, escribí un código completo según cómo deseo usar el método Process.Start.

Uso

Lo he usado para crear un diálogo de progreso alrededor de 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);
                }
            }
        }
    }
}

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

De todos modos, la solución que funcionó para mí fue usar diferentes subprocesos para leer el StandardOutput y el 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 = <*>quot;...";
                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(<*>quot;Failed run... blah blah");
                    }
                }
                else
                {
                    throw new Exception(<*>quot;process timed out after waiting {timeout}");
                }
            }
            catch (Exception e)
            {
                throw new Exception(<*>quot;Failed to succesfully run the process.....", e);
            }
        }
    }

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

Después de leer todos los mensajes aquí, me decidí por la solución consolidada de Marko Avlija & # 353 ;. Sin embargo, no resolvió todos mis problemas.

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

Así que probé 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

La solución que terminé usando para evitar toda la complejidad:

var outputFile = Path.GetTempFileName();
info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents

Así que creo un archivo temporal, redirijo tanto el resultado como el error al usar > archivo de salida > 2 > & amp; 1 y luego simplemente lea el archivo una vez que el proceso haya finalizado.

Las otras soluciones están bien para escenarios en los que desea hacer otras cosas con la salida, pero para cosas simples esto evita mucha complejidad.

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

Creo que con async, es posible tener una solución más elegante y no tener interbloqueos incluso cuando se utiliza tanto StandardOutput como standardError:

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;

    process.Start();

    var tStandardOutput = process.StandardOutput.ReadToEndAsync();
    var tStandardError = process.StandardError.ReadToEndAsync();

    if (process.WaitForExit(timeout))
    {
        string output = await tStandardOutput;
        string errors = await tStandardError;

        // Process completed. Check process.ExitCode here.
    }
    else
    {
        // Timed out.
    }
}

Se basa en la respuesta de Mark Byers. Si no está en un método asíncrono, puede usar string output = tStandardOutput.result; en lugar de await

Llamemos al código de muestra publicado aquí el redirector y al otro programa el redirigido. Si fuera yo, probablemente escribiría un programa redirigido de prueba que se puede usar para duplicar el problema.

Así lo hice. Para los datos de prueba utilicé la especificación de lenguaje ECMA-334 C # v PDF; se trata de 5MB. La siguiente es la parte importante de eso.

StreamReader stream = null;
try { stream = new StreamReader(Path); }
catch (Exception ex)
{
    Console.Error.WriteLine("Input open error: " + ex.Message);
    return;
}
Console.SetIn(stream);
int datasize = 0;
try
{
    string record = Console.ReadLine();
    while (record != null)
    {
        datasize += record.Length + 2;
        record = Console.ReadLine();
        Console.WriteLine(record);
    }
}
catch (Exception ex)
{
    Console.Error.WriteLine(
ProcessStartInfo info = new ProcessStartInfo(TheProgram);
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
string record = p.StandardOutput.ReadLine();
while (record != null)
{
    Console.WriteLine(record);
    record = p.StandardOutput.ReadLine();
}
p.WaitForExit();
quot;Error: {ex.Message}"); return; }

El valor del tamaño de datos no coincide con el tamaño real del archivo, pero eso no importa. No está claro si un archivo PDF siempre usa tanto CR como LF al final de las líneas, pero eso no importa. Puede usar cualquier otro archivo de texto grande para probar.

Usando eso, el código del redirector de muestra se cuelga cuando escribo la gran cantidad de datos pero no cuando escribo una pequeña cantidad.

Intenté mucho rastrear de alguna manera la ejecución de ese código y no pude. Comenté las líneas del programa redirigido que desactivaron la creación de una consola para que el programa redirigido intentara obtener una ventana de consola separada pero no pude.

Luego encontré Cómo iniciar una aplicación de consola en una nueva ventana, la ventana de los padres o ninguna ventana . Aparentemente, no podemos (fácilmente) tener una consola separada cuando un programa de consola inicia otro programa de consola sin ShellExecute y dado que ShellExecute no admite la redirección, debemos compartir una consola, incluso si no especificamos ninguna ventana para el otro proceso.

Supongo que si el programa redirigido llena un búfer en algún lugar, debe esperar a que se lean los datos y si en ese momento el redirector no lee ningún dato, significa que hay un punto muerto.

La solución es no usar ReadToEnd y leer los datos mientras se escriben, pero no es necesario usar lecturas asincrónicas. La solución puede ser bastante simple. Lo siguiente funciona para mí con el PDF de 5 MB.

<*>

Otra posibilidad es usar un programa GUI para hacer la redirección. El código anterior funciona en una aplicación WPF excepto con modificaciones obvias.

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 es mucho mejor que el programa esperando que no 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top