Question

J'ai le code suivant:

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

Je sais que le résultat du processus que je commence est d’environ 7 Mo. L'exécuter dans la console Windows fonctionne bien. Malheureusement, par programmation, cela se bloque indéfiniment chez WaitForExit. Notez aussi que le code ne se bloque PAS pour les sorties plus petites (comme 3 Ko).

Est-il possible que le StandardOutput interne de ProcessStartInfo ne puisse pas mettre en mémoire tampon 7 Mo? Si oui, que devrais-je faire à la place? Si non, qu'est-ce que je fais mal?

Était-ce utile?

La solution

Le problème est que si vous redirigez StandardOutput et / ou StandardError , la mémoire tampon interne peut devenir saturée. Quel que soit votre ordre, il peut y avoir un problème:

  • Si vous attendez que le processus se termine avant de lire StandardOutput , le processus peut bloquer toute tentative d'écriture, le processus ne se termine donc jamais.
  • Si vous lisez à partir de StandardOutput à l'aide de ReadToEnd, votre processus peut bloquer si le processus ne ferme jamais StandardOutput (par exemple, s'il ne se termine jamais, ou si elle est bloquée, l'écriture dans StandardError ).

La solution consiste à utiliser des lectures asynchrones pour garantir que la mémoire tampon ne soit pas saturée. Pour éviter les blocages et récupérer toutes les sorties de StandardOutput et de StandardError , procédez comme suit:

EDIT: voir les réponses ci-dessous pour savoir comment éviter une ObjectDisposedException si le délai d'attente est dépassé.

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

Autres conseils

La documentation sur Process.StandardOutput dit de lire avant d'attendre, sinon vous pouvez bloquer, extrait copié ci-dessous:

 // 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 réponse de Mark Byers est excellente, mais j’ajouterais simplement ceci: les délégués OutputDataReceived et ErrorDataReceived doivent être supprimés avant que les paramètres outputWaitHandle et errorWaitHandle ne soient supprimés. Si le processus continue à émettre des données après l'expiration du délai imparti, puis se termine, les variables outputWaitHandle et errorWaitHandle seront accessibles après avoir été supprimées.

(Pour info, j'ai dû ajouter cette mise en garde comme réponse car je ne pouvais pas commenter son message.)

Le problème avec l'exception ObjectDisposedException non gérée se produit lorsque le processus a expiré. Dans ce cas, les autres éléments de la condition:

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

ne sont pas exécutés. J'ai résolu ce problème de la manière suivante:

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

Il s'agit d'une solution plus moderne, basée sur une bibliothèque parallèle de tâches (TPL) pour les versions .NET 4.5 et ultérieures, plus moderne.

Exemple d'utilisation

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!"); }

Mise en œuvre

<*>

Rob a répondu et m'a sauvé quelques heures d'essais supplémentaires. Lire le tampon de sortie / erreur avant d’attendre:

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

Nous avons également ce problème (ou une variante).

Essayez ce qui suit:

1) Ajoutez un délai d’attente à p.WaitForExit (nnnn); où nnnn est exprimé en millisecondes.

2) Placez l'appel ReadToEnd avant l'appel WaitForExit. Ceci correspond à ce que MS nous a recommandé.

Remerciements à EM0 pour https://stackoverflow.com/a/17600012/4151626

Les autres solutions (y compris les EM0) sont toujours bloquées pour mon application, en raison de délais d'attente internes et de l'utilisation de StandardOutput et de StandardError par l'application générée. Voici ce qui a fonctionné pour moi:

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

Edition: ajout de l'initialisation de StartInfo à l'exemple de code

Je l'ai résolu de cette façon:

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

J'ai redirigé les entrées, les sorties et les erreurs et géré la lecture à partir des flux de sortie et d'erreur. Cette solution fonctionne pour le SDK 7- 8.1, pour Windows 7 et Windows 8

.

Ce message est peut-être obsolète, mais j'ai découvert la principale raison pour laquelle il se bloque en raison d'un débordement de pile pour le redirectStandardoutput ou si vous avez redirectStandarderror.

Comme les données de sortie ou les données d'erreur sont volumineuses, cela entraînera un temps d'arrêt, car le traitement est toujours en cours pour une durée indéterminée.

pour résoudre ce problème:

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

J'ai essayé de créer une classe qui résoudrait votre problème en utilisant la lecture de flux asynchrone, en tenant compte des réponses de Mark Byers, Rob, stevejay. Ce faisant, j'ai réalisé qu'il existait un bogue lié à la lecture du flux de sortie du processus asynchrone.

J'ai signalé ce bogue à Microsoft: https://connect.microsoft.com/ VisualStudio / feedback / details / 3119134

Résumé:

  

Vous ne pouvez pas faire cela:

     

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

     

Vous recevrez une exception System.InvalidOperationException: StandardOut   n'a pas été redirigé ou le processus n'a pas encore commencé.

     

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

     

Ensuite, vous devez démarrer la sortie asynchrone lue une fois le processus terminé.   a commencé:

     

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

     

Ce faisant, créez une situation de concurrence critique car le flux de sortie peut recevoir   données avant de le définir sur asynchrone:

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

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

     

Ensuite, certaines personnes pourraient dire qu'il vous suffit de lire le flux   avant de le définir sur asynchrone. Mais le même problème se pose. Là   sera une condition de concurrence entre la lecture synchrone et définir la   diffuser en mode asynchrone.

     

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

     

Il n’existe aucun moyen d’obtenir une lecture asynchrone sécurisée d’un flux de sortie.   d'un processus à la manière réelle " Processus " et " ProcessStartInfo " a   été conçu.

Vous utilisez probablement mieux la lecture asynchrone comme suggéré par d’autres utilisateurs pour votre cas. Toutefois, sachez que vous risquez de manquer certaines informations en raison de l’état de concurrence.

Aucune des réponses ci-dessus ne fait le travail.

La solution de Rob se bloque et la solution de "Mark Byers" obtient l'exception classée (j'ai essayé les "solutions" des autres réponses).

J'ai donc décidé de suggérer une autre solution:

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

Ce code a été débogué et fonctionne parfaitement.

Introduction

La réponse actuellement acceptée ne fonctionne pas (exception au lancement) et il existe trop de solutions de contournement, mais pas de code complet. C’est évidemment une perte de temps pour beaucoup de gens parce que c’est une question populaire.

En combinant la réponse de Mark Byers et celle de Karol Tyl, j'ai écrit le code complet en fonction de la manière dont je souhaite utiliser la méthode Process.Start.

Utilisation

Je l'ai utilisé pour créer un dialogue de progression autour des commandes git. Voici comment je l'ai utilisé:

    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 théorie, vous pouvez également combiner stdout et stderr, mais je n'ai pas testé cela.

Code

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

Je sais que notre dîner est vieux, mais après avoir lu toute cette page, aucune des solutions ne fonctionnait pour moi, même si je n'ai pas essayé Muhammad Rehan car le code était un peu difficile à suivre, même si je suppose qu'il était sur la bonne piste. Lorsque je dis que cela n'a pas fonctionné, ce n'est pas tout à fait vrai. Parfois, cela fonctionnerait bien. Je suppose que cela a quelque chose à voir avec la longueur de la sortie avant une marque EOF.

Quoi qu’il en soit, la solution qui a fonctionné pour moi a été d’utiliser différents threads pour lire StandardOutput et StandardError et écrire les messages.

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

J'espère que cela aidera quelqu'un qui pensait que cela pourrait être si difficile!

Après avoir lu tous les articles, j’ai opté pour la solution consolidée de Marko Avlijaš. Cependant , cela n'a pas résolu tous mes problèmes.

Dans notre environnement, nous avons un service Windows qui est programmé pour exécuter des centaines de fichiers .bat, cmd, .exe, etc., différents, qui se sont accumulés au fil des ans et qui ont été écrits par de nombreuses personnes et dans des styles différents. Nous n’avons aucun contrôle sur l’écriture des programmes & amp; scripts, nous ne sommes responsables que de la planification, de l’exécution et du reporting des succès / échecs.

J'ai donc essayé à peu près toutes les suggestions ici avec différents niveaux de succès. La réponse de Marko était presque parfaite, mais lorsqu'elle était exécutée en tant que service, elle ne capturait pas toujours stdout. Je n'ai jamais compris pourquoi, sinon.

La seule solution que nous ayons trouvée qui fonctionne dans TOUS nos cas est la suivante: http://csharptest.net/319/using-the-processrunner-class/index.html

Solution que j'ai finalement utilisée pour éviter toute complexité:

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

Je crée donc un fichier temporaire, je redirige à la fois la sortie et l’erreur en utilisant > fichier de sortie > 2 > & amp; 1 , puis lisez le fichier à la fin du processus.

Les autres solutions conviennent aux scénarios dans lesquels vous souhaitez effectuer d'autres tâches avec la sortie, mais pour les tâches simples, cela évite beaucoup de complexité.

Je pense que c'est une approche simple et meilleure (nous n'avons pas besoin de 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;
    }
}

Je pense qu'avec async, il est possible d'avoir une solution plus élégante et de ne pas avoir de blocage, même en utilisant à la fois standardOutput et 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.
    }
}

Il est basé sur la réponse de Mark Byers. Si vous n'êtes pas dans une méthode asynchrone, vous pouvez utiliser chaîne output = tStandardOutput.result; au lieu de wait

.

Appelons l’exemple de code posté ici le redirecteur et l’autre programme le redirigé. Si c’était moi, j’écrirais probablement un programme de test redirigé qui pourrait être utilisé pour reproduire le problème.

Alors je l'ai fait. Pour les données de test, j'ai utilisé le fichier PDF ECMA-334 C # Language Specificationv; c'est environ 5MB. Ce qui suit est la partie importante de cela.

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

La valeur de la taille de données ne correspond pas à la taille réelle du fichier, mais cela n'a pas d'importance. Il n'est pas clair si un fichier PDF utilise toujours à la fois CR et LF à la fin des lignes, mais cela n'a pas d'importance. Vous pouvez utiliser n’importe quel autre fichier texte volumineux pour effectuer un test.

L’exemple de code de redirecteur se bloque lorsque j’écris une grande quantité de données, mais pas lorsque j’écris une petite quantité.

J'ai beaucoup essayé de suivre en quelque sorte l'exécution de ce code et je ne pouvais pas. J'ai commenté les lignes du programme redirigé qui désactivait la création d'une console pour que le programme redirigé tente d'obtenir une fenêtre de console séparée, mais je ne pouvais pas.

Ensuite, j'ai trouvé Comment démarrer une application console dans une nouvelle fenêtre, la fenêtre du parent ou aucune fenêtre . Donc, apparemment, nous ne pouvons pas (facilement) avoir une console séparée lorsqu'un programme de console démarre un autre programme de console sans ShellExecute et, comme ShellExecute ne prend pas en charge la redirection, nous devons partager une console, même si nous ne spécifions aucune fenêtre pour l'autre processus.

Je suppose que si le programme redirigé remplit une mémoire tampon quelque part, il doit attendre que les données soient lues et si, à ce stade, aucune donnée n'est lue par le redirecteur, il s'agit d'un blocage.

La solution consiste à ne pas utiliser ReadToEnd et à lire les données en cours d'écriture, mais il n'est pas nécessaire d'utiliser des lectures asynchrones. La solution peut être assez simple. Ce qui suit fonctionne pour moi avec le PDF de 5 Mo.

<*>

Une autre possibilité consiste à utiliser un programme graphique pour effectuer la redirection. Le code précédent fonctionne dans une application WPF sauf avec des modifications évidentes.

J'avais le même problème, mais la raison était différente. Cela se produirait cependant sous Windows 8, mais pas sous Windows 7. La ligne suivante semble avoir causé le problème.

pProcess.StartInfo.UseShellExecute = False

La solution consistait à NE PAS désactiver UseShellExecute. Je viens de recevoir une fenêtre contextuelle de Shell, ce qui est indésirable, mais beaucoup mieux que le programme n'attend rien de particulier. J'ai donc ajouté la solution suivante pour cela:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

La seule chose qui me préoccupe maintenant est de savoir pourquoi cela se produit sous Windows 8.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top