218 votes

ProcessStartInfo accroché « WaitForExit » ? Pourquoi ?

J’ai le code suivant :

Je sais que la sortie du processus que je commence est longue d’environ 7 Mo. En l’exécutant dans la console Windows fonctionne très bien. Malheureusement par programme cela se bloque indéfiniment à WaitForExit. Notez que également cela ne code pas se bloquer pour des sorties plus petits (comme 3KO).

Est-il possible que le StandardOutput interne en ProcessStartInfo ne peut mettre en mémoire tampon 7MB ? Dans l’affirmative, que dois-je faire à la place ? Si non, ce que je fais mal ?

462voto

Mark Byers Points 318575

Le problème est que si vous rediriger StandardOutput et/ou StandardError la mémoire tampon interne peut devenir complet. Quelle que soit la commande que vous utilisez, il peut être un problème:

  • Si vous attendez que le processus de sortie avant de lire StandardOutput le processus peut bloquer à essayer d'écrire, de sorte que le processus ne se termine jamais.
  • Si vous lisez à partir d' StandardOutput à l'aide de ReadToEnd ensuite, votre processus peut bloquer si le processus ne se referme jamais StandardOutput (par exemple si elle ne se termine, ou si elle est bloquée par écrit à l' StandardError).

La solution est d'utiliser asynchrone lit pour s'assurer que le tampon de ne pas obtenir le plein. Pour éviter toute blocages et de recueillir toutes les données de sortie à partir de deux StandardOutput et StandardError vous pouvez faire:

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

111voto

Rob Points 24505

La documentation de Process.StandardOutput indique de lire avant d'attendre, sinon vous pouvez vous bloquer, l'extrait de code est 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();
 

25voto

stevejay Points 78

La réponse de Mark Byers est excellente, mais je voudrais juste ajouter ce qui suit: les délégués OutputDataReceived et ErrorDataReceived doivent être supprimés avant que les fichiers outputWaitHandle et errorWaitHandle soient éliminés. Si le processus continue à générer des données après le dépassement du délai d'attente, puis se termine, les variables outputWaitHandle et errorWaitHandle sont accessibles après avoir été éliminées.

(FYI j'ai dû ajouter cette mise en garde comme une réponse car je ne pouvais pas commenter son post.)

8voto

torial Points 9883

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 en millisecondes.

2) Placez l'appel ReadToEnd avant l'appel WaitForExit. Voilà ce que nous avons vu MS recommander.

4voto

Jeffrey Knight Points 2654

Une note de prudence avec la solution Mark Byers :

entrer la description de l'image ici

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X