StandardOutput.ReadToEnd () se bloque

J’ai un programme qui utilise fréquemment un programme externe et lit ses sorties. Cela fonctionne assez bien en utilisant votre sortie de redirection de processus habituelle, mais un argument spécifique pour une raison quelconque se bloque quand j’essaie de le lire, pas de message d’erreur – aucune exception, il s’arrête juste lorsqu’il atteint cette ligne. J’utilise bien sûr une fonction centralisée pour appeler et lire les sorties du programme, qui est la suivante:

public ssortingng ADBShell(ssortingng adbInput) { try { //Create Empty values ssortingng result = ssortingng.Empty; ssortingng error = ssortingng.Empty; ssortingng output = ssortingng.Empty; System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo(toolPath + "adb.exe"); procStartInfo.Arguments = adbInput; procStartInfo.RedirectStandardOutput = true; procStartInfo.RedirectStandardError = true; procStartInfo.UseShellExecute = false; procStartInfo.CreateNoWindow = true; procStartInfo.WorkingDirectory = toolPath; System.Diagnostics.Process proc = new System.Diagnostics.Process(); proc.StartInfo = procStartInfo; proc.Start(); // Get the output into a ssortingng proc.WaitForExit(); result = proc.StandardOutput.ReadToEnd(); error = proc.StandardError.ReadToEnd(); //Some ADB outputs use this if (result.Length > 1) { output += result; } if (error.Length > 1) { output += error; } Return output; } catch (Exception objException) { throw objException; } } 

La ligne qui se bloque est result = proc.StandardOutput.ReadToEnd(); , mais encore une fois, pas à chaque fois, uniquement lorsque envoyé un argument spécifique (“start-server”). Tous les autres arguments fonctionnent correctement – il lit la valeur et la retourne. C’est aussi étrange la façon dont il se bloque. Il ne gèle pas ou ne donne aucune erreur, il arrête juste le traitement. Comme s’il s’agissait d’une commande ‘return’, sauf qu’elle ne retourne même pas à la fonction appelante, elle arrête tout ce qui a une interface toujours active. Quelqu’un a déjà vécu ça? Quelqu’un a une idée de ce que je devrais essayer? Je suppose que c’est quelque chose d’inattendu dans le stream lui-même, mais y a-t-il un moyen de le gérer / ignorer pour qu’il le lise quand même?

Les solutions proposées avec BeginOutputReadLine() sont un bon moyen mais dans des situations comme celle-ci, elles ne sont pas applicables, car le processus (certainement avec l’utilisation de WaitForExit() ) se termine avant que la sortie asynchrone ne soit complètement terminée.

J’ai donc essayé de l’implémenter de manière synchrone et j’ai constaté que la solution consistait à utiliser la méthode Peek() de la classe StreamReader . J’ai ajouté une vérification pour Peek() > -1 pour être sûr que ce n’est pas la fin du stream, comme dans l’ article décrit dans MSDN, et finalement cela fonctionne et arrête de bloquer!

Voici le code:

 var process = new Process(); process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.WorkingDirectory = @"C:\test\"; process.StartInfo.FileName = "test.exe"; process.StartInfo.Arguments = "your arguments here"; process.Start(); var output = new List(); while (process.StandardOutput.Peek() > -1) { output.Add(process.StandardOutput.ReadLine()); } while (process.StandardError.Peek() > -1) { output.Add(process.StandardError.ReadLine()); } process.WaitForExit(); 

Le problème est que vous utilisez les méthodes ReadToEnd synchrones sur les stream StandardOutput et StandardError . Cela peut conduire à une impasse potentielle que vous rencontrez. Ceci est même décrit dans le MSDN . La solution est décrite ici. Fondamentalement, il s’agit de: Utiliser la version asynchrone BeginOutputReadLine pour lire les données du stream StandardOutput :

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

Implémentation de la lecture asynchrone en utilisant BeginOutputReadLine voir dans ProcessStartInfo suspendu à “WaitForExit”? Pourquoi?

Qu’en est-il de quelque chose comme:

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

J’ai eu le même problème de blocage. Cet extrait de code a fonctionné pour moi.

  ProcessStartInfo startInfo = new ProcessStartInfo("cmd") { WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, CreateNoWindow = true }; Process process = new Process(); process.StartInfo = startInfo; process.Start(); process.StandardInput.WriteLine("echo hi"); process.StandardInput.WriteLine("exit"); var output = process.StandardOutput.ReadToEnd(); process.Dispose(); 

Quelque chose d’élégant et de travaillé pour moi est:

 Process nslookup = new Process() { StartInfo = new ProcessStartInfo("nslookup") { RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden } }; nslookup.Start(); nslookup.StandardInput.WriteLine("set type=srv"); nslookup.StandardInput.WriteLine("_ldap._tcp.domain.local"); nslookup.StandardInput.Flush(); nslookup.StandardInput.Close(); ssortingng output = nslookup.StandardOutput.ReadToEnd(); nslookup.WaitForExit(); nslookup.Close(); 

Cette réponse que j’ai trouvée ici et le truc utilise Flush() et Close() sur l’entrée standard.

J’ai eu le même genre de problème que l’erreur était juste en suspens.

D’après votre réponse à Daniel Hilgarth, je n’ai même pas essayé d’utiliser ces codes, même si je pense qu’ils auraient travaillé pour moi.

Puisque je veux être capable de produire des résultats encore plus sophistiqués, j’ai finalement décidé que je le ferais avec les deux sorties en cours.

 public static class RunCommands { #region Outputs Property private static object _outputsLockObject; private static object OutputsLockObject { get { if (_outputsLockObject == null) Interlocked.CompareExchange(ref _outputsLockObject, new object(), null); return _outputsLockObject; } } private static Dictionary _outputs; private static Dictionary Outputs { get { if (_outputs != null) return _outputs; lock (OutputsLockObject) { _outputs = new Dictionary(); } return _outputs; } } #endregion public static ssortingng GetCommandOutputSimple(ProcessStartInfo info, bool returnErrorIfPopulated = true) { // Redirect the output stream of the child process. info.UseShellExecute = false; info.CreateNoWindow = true; info.RedirectStandardOutput = true; info.RedirectStandardError = true; var process = new Process(); process.StartInfo = info; process.ErrorDataReceived += ErrorDataHandler; process.OutputDataReceived += OutputDataHandler; var output = new CommandOutput(); Outputs.Add(process, output); process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); // Wait for the process to finish reading from error and output before it is finished process.WaitForExit(); Outputs.Remove(process); if (returnErrorIfPopulated && (!Ssortingng.IsNullOrWhiteSpace(output.Error))) { return output.Error.TrimEnd('\n'); } return output.Output.TrimEnd('\n'); } private static void ErrorDataHandler(object sendingProcess, DataReceivedEventArgs errLine) { if (errLine.Data == null) return; if (!Outputs.ContainsKey(sendingProcess)) return; var commandOutput = Outputs[sendingProcess]; commandOutput.Error = commandOutput.Error + errLine.Data + "\n"; } private static void OutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine) { if (outputLine.Data == null) return; if (!Outputs.ContainsKey(sendingProcess)) return; var commandOutput = Outputs[sendingProcess]; commandOutput.Output = commandOutput.Output + outputLine.Data + "\n"; } } public class CommandOutput { public ssortingng Error { get; set; } public ssortingng Output { get; set; } public CommandOutput() { Error = ""; Output = ""; } } 

Cela a fonctionné pour moi et m’a permis de ne pas avoir à utiliser un délai d’attente pour la lecture.

La solution de la réponse acceptée n’a pas fonctionné pour moi. J’ai dû utiliser des tâches afin d’éviter l’impasse:

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

Avec une fonction GetStreamOutput comme suit:

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

Juste au cas où quelqu’un s’interrogerait sur cette question en utilisant Windows Forms et TextBox (ou RichTextBox ) pour afficher les erreurs et afficher les résultats du processus en temps réel (car ils sont écrits dans process.StandardOutput / process.StandardError ).

Vous devez utiliser OutputDataReceived() / ErrorDataReceived() pour lire les deux stream sans blocage, il n’ya aucun moyen (pour autant que je sache) d’éviter les blocages, même la réponse de Fedor, qui contient maintenant le tag “Answer” et le la plupart aime à jour, ne fait pas le tour pour moi.

Cependant, lorsque vous utilisez le RichTextBox (ou TextBox) pour générer les données, vous rencontrez également un autre problème, à savoir comment écrire les données dans la zone de texte en temps réel (une fois qu’il arrive). Vous recevez l’access aux données à l’intérieur de l’un des threads d’arrière-plan OutputDataReceived() / ErrorDataReceived() et vous ne pouvez que AppendText() partir du thread principal.

Ce que j’ai d’abord essayé, c’est d’appeler process.Start() depuis un thread d’arrière-plan, puis d’appeler BeginInvoke() => AppendText() dans les OutputDataReceived() / ErrorDataReceived() alors que le thread principal était process.WaitForExit() .

Cependant, cela a conduit à mon gel de la forme et finalement à la suspension pour l’éternité. Après quelques jours d’essais, je me suis retrouvé avec la solution ci-dessous, qui semble fonctionner plutôt bien.

En OutputDataReceived() , vous devez append les messages dans une collection concurrente à l’intérieur des OutputDataReceived() / ErrorDataReceived() tandis que le thread principal doit constamment essayer d’extraire les messages de cette collection et les append dans la zone de texte:

  ProcessStartInfo startInfo = new ProcessStartInfo(File, mysqldumpCommand); process.StartInfo.FileName = File; process.StartInfo.Arguments = mysqldumpCommand; process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.StartInfo.RedirectStandardInput = false; process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.StandardErrorEncoding = Encoding.UTF8; process.StartInfo.StandardOutputEncoding = Encoding.UTF8; process.EnableRaisingEvents = true; ConcurrentQueue messages = new ConcurrentQueue(); process.ErrorDataReceived += (object se, DataReceivedEventArgs ar) => { ssortingng data = ar.Data; if (!ssortingng.IsNullOrWhiteSpace(data)) messages.Enqueue(data); }; process.OutputDataReceived += (object se, DataReceivedEventArgs ar) => { ssortingng data = ar.Data; if (!ssortingng.IsNullOrWhiteSpace(data)) messages.Enqueue(data); }; process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); while (!process.HasExited) { ssortingng data = null; if (messages.TryDequeue(out data)) UpdateOutputText(data, tbOutput); Thread.Sleep(5); } process.WaitForExit(); 

Le seul inconvénient de cette approche est le fait que vous pouvez perdre des messages dans un cas assez rare, lorsque le processus commence à les écrire entre process.Start() et process.BeginErrorReadLine() / process.BeginOutputReadLine() . La seule façon d’éviter cela est de lire les stream complets et (ou) d’y accéder uniquement lorsque le processus est terminé.

premier

  // 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. ssortingng output = p.StandardOutput.ReadToEnd(); p.WaitForExit(); 

seconde

  // Do not perform a synchronous read to the end of both // redirected streams. // ssortingng output = p.StandardOutput.ReadToEnd(); // ssortingng error = p.StandardError.ReadToEnd(); // p.WaitForExit(); // Use asynchronous read operations on at least one of the streams. p.BeginOutputReadLine(); ssortingng error = p.StandardError.ReadToEnd(); p.WaitForExit(); 

Ceci est de MSDN