none
Utiliser correctement RedirectStandardError RRS feed

  • Question

  • Bonjour,

    Quelle est la bonne utilisation de la redirection de la sortie d'erreur.

    Celle-ci :  

     Using convProcess As New Process
    
    dim errmsg as String
    
      With convProcess
    
      .StartInfo.FileName = "toto.exe"
      .StartInfo.CreateNoWindow = True
      .StartInfo.WindowStyle = ProcessWindowStyle.Hidden
      .StartInfo.UseShellExecute = False
      .StartInfo.RedirectStandardError = True
    
    
      .Start()
      errmsg = .StandardError.ReadToEnd
    
      .WaitForExit(3000)
    
      If Not .HasExited Then
       .Kill()
       .WaitForExit()
      ElseIf convProcess.ExitCode <> 0 Then
    
       Throw New Exception(errMsg)
      End If
    
      .Close()
    
      End With
    
    

    ou celle là :

     Using convProcess As New Process
    
      With convProcess
    
      .StartInfo.FileName = "toto.exe"
      .StartInfo.CreateNoWindow = True
      .StartInfo.WindowStyle = ProcessWindowStyle.Hidden
      .StartInfo.UseShellExecute = False
      .StartInfo.RedirectStandardError = True
    
      .Start()
    
      .WaitForExit(3000)
    
      If Not .HasExited Then
       .Kill()
       .WaitForExit()
    
      ElseIf convProcess.ExitCode <> 0 Then
    
      Dim errmsg as string = .StandardError.ReadToEnd
       Throw New Exception(errMsg)
      End If
    .Close() End With
    Cordialement
    Mael
    vendredi 23 avril 2010 12:15

Réponses

  • Bonjour,

    Voici une proposition pour une lecture asynchrone dans votre cas, c'est pas trop "lourd" à mettre en oeuvre... :

    ProcessStartInfo info;
    
    info = new ProcessStartInfo();
    info.RedirectStandardError = true;
    info.UseShellExecute = false;
    // Compléter info
    
    using (Process p = Process.Start(info))
    {
      Thread t;
      StringBuilder sb;
    
      sb = new StringBuilder();
    
      t = new Thread(() =>
      {
        string s;
    
        s = p.StandardError.ReadLine();
        while (s != null)
        {
          sb.AppendLine();
        }
      }).Start();
    
      p.WaitForExit(3000);
    
      if (t.IsAlive == true)
      {
        t.Abort();
      }
    
      string donnéeRécupérée = sb.ToString();        
    }

    Cordialement

     


    Gilles TOURREAU - MVP C# - Architecte .NET/Consultant/Formateur
    mardi 27 avril 2010 11:28
    Modérateur

Toutes les réponses

  • Désolé pour la taille de la police sur la fin post mais je n'arrive pas à la corriger. :(
    vendredi 23 avril 2010 12:23
  • Bonjour,

    Je déconseille l'utilisation de la méthode Process.Kill().

    Voici un article que j'avais publié qui explique comment récupérer la sortie standard/erreur en synchrone et asynchrone.

    Cordialement


    Gilles TOURREAU - MVP C# - Architecte .NET/Consultant/Formateur
    lundi 26 avril 2010 06:49
    Modérateur
  • Bonjour,

    Merci pour ta réponse, j'ai trouvé l'article.
    Par contre je ne trouve rien qui semble me conseiller plus une voie que l'autre (je serais en synchrone moi c'est sur).

    MSDN semblait déconseiller de mettre le ReadToEnd aprés le WaitForExit pour des pb de blocage.
    Personnellement mettre le ReadToEnd dans la gestion d'erreur au cas ou le ExitCode est <> 0 me semble plus logique car :

    Si on met le ReadToEnd avant le WaitForExit le WaitForExit ne sert a rien... l'application sera bloqué par le ReadToEnd. Donc si mon process ne répond plus, il restera indéfiniment bloqué sur le ReadToEnd alors qu'avec l'autre solution non.

    Je suis un peu dans le flou là. :S

    Pour ce qui est du Kill, c'est simplement au cas où le process ne répondrait pas, pour ne pas bloquer le programme.
    Ce n'est pas le cas normal, d'ailleurs dans les millier de test realisés, je n'ai jamais eu à exécuté ce code.
    (Initialement je ne recupérais pas la sortie, je faisais simplement un .Start puis WaitForExit et ensuite le test sur le HasExited).

    Que faire si le process ne répond pas sans passer par WaitForExit et Kill ?

    Cordialement
    Mael

    lundi 26 avril 2010 08:07
  • Bonjour,

    Le choix du synchrone/asynchrone dépend uniquement si votre application doit attendre la fin de l'application lancée.

    Dans votre cas, préférez le mode asynchrone, cela empêche de bloquer votre application tant que l'application lancée n'est pas terminée. Il n'est pas conseillé de faire des ReadToEnd() car cela vous oblige à attendre la fin du processus lancé pour afficher toute la sortie. Préférez donc plusieurs lectures dans une boucle (le tout en asynchrone).

    Dans votre cas le choix du Kill() est justifié, car je n'avais pas vu que vous spécifiez une durée de 3 secondes dans le WaitForExit(). Si vous jugez qu'au bout de 3 secondes, l'application lancée est plantée si elle n'est pas quitté, vous devez dans ce cas utilisez Kill().

    Cordialement


    Gilles TOURREAU - MVP C# - Architecte .NET/Consultant/Formateur
    lundi 26 avril 2010 11:38
    Modérateur
  • Merci.

    Le mode synchrone est necessaire pour moi, je me pose simplement la question de où "coller" le ReadToEnd de la sortie d'erreur car MSDN déconseillé le placer aprés le WaitForExit car cela peut entrainer des blocages :

    "Une condition de blocage peut se produire si le processus parent appelle p.WaitForExit avant p.StandardError.ReadToEnd et le processus enfant écrit suffisamment de texte pour remplir le flux redirigé. Le processus parent attend indéfiniment que le processus enfant se termine. Le processus enfant attend indéfiniment que le parent lise le flux StandardError en entier."

    http://msdn.microsoft.com/fr-fr/library/system.diagnostics.processstartinfo.redirectstandarderror(VS.80).aspx

    Pour ma part je n'ai pas besoin de lire cette sortie si tout ce passe bien, (Exitcode=0) du coup le mettre aprés le WaitForExit dans la condition où convProcess.ExitCode <> 0 me semble logique (avant le Throw Exception pour justement passer le message d'erreur dans l'exception). Cependant il ne faut pas que cela pose un pb potentiel.

    J'ai testé sur un petit jeu d'essaie cela ne semble pas poser de pb de faire le ReadToEnd aprés le WaitForExit...

    En tout cas merci de votre aide.

    Cordialement
    Mael

    lundi 26 avril 2010 12:09
  • Bonjour,

    Je vous déconseille d'utiliser la méthode ReadToEnd() après un WaitForExit(). Ce que dit le MSDN est vrai dans le cas où l'application lancée produit énormément de données dans la sortie. Dans ce cas, l'application parent est automatiquement et l'application enfant attent que le parent "purge" la sortie. Hors comme, votre application parent attend la fin de l'enfant, il y a une attente mutuelle entre ces deux applications. Bien entendu ce blocage ne se produit pas si les données produites en sorties ne sont pas importante (j'ai essayé sur un Vista, et j'ai un blocage lorsque la sortie produit 3Ko-4Ko de données).

    Je conseille d'utiliser dans votre cas le mode asynchrone (dans un thread) afin de purger en continue la sortie de votre application cliente. Une fois que la méthode WaitForExit() est débloqué (à cause de la fin, plantage ou time out de l'application), vous récupérez ce que vous avez automatiquement lu.

    Cordialement


    Gilles TOURREAU - MVP C# - Architecte .NET/Consultant/Formateur
    mardi 27 avril 2010 07:06
    Modérateur
  • D'accord, j'abadonne le ReadToEnd dans la gestion d'erreur.
    Je suis convaincu.

    Je suis aussi tombé sur cet article qui explique bien le problème et s'attarde sur les "effets" du ReadToEnd.
    http://www.boyet.com/Articles/CapturingStandardOutput.html

    Indiquant qu'avec un ReadToEnd le WaitForExit devient décoratif car il le ReadToEnd va attendre la fin de l'enfant.

    "In fact, I'd argue the call to WaitForExit() is not strictly necessary. The pipe will only close once the child has finished. When ReadToEnd() completes, the child has already terminated (modulo a millisecond or two), and so there's no point in waiting for it to finish: you have all the child's standard output already."

    Le seul petit hic c'est que si mon process ne répond plus, ça va bloquer sur le ReadToEnd et pas sur le WaitForExit.
    Du coup l'application (qui sera une tâche planifiée) va rester indéfiniment bloquée. Sauf peut être si le flux est fermé et qu'aprés seulement le process ne réponde plus (peu probable), chose qui est loin d'être sur car le ReadToEnd semble attendre la fermeture du flux ET la fin du processus.

    Implementer un model asynchrone dans ce que j'ai à faire est vraiment "lourd" pour "si peu" même si avec ça mon pb d'application qui répondu plus serait réglée.

    Le process est un exe de convertion d'image d'un format à l'autre, dans votre article vous posiez la question "Que choisir entre synchrone et asynchrone ?" avec comme proposition de réponse "Si vous souhaitez exécuter un processus et attendre sa fin pour obtenir sa sortie (par exemple l’exécution d’un compilateur), dans ce cas préférez la méthode synchrone.".

    C'est typiquement mon cas, je lance mon process et je veux simplement savoir si ça c'est bien passé ou non et dans ce cas avoir le message d'erreur. Je ne m'occupe pas de la sortie standart, simplement celle d'erreur, la probabilité que l'exe ne réponde plus est quasi nulle...  mais comme je suis perfectionniste je l'avais envisagé. :)

    Merci de votre aide et de vos explications.

    Cordialement
    Mael

     

    mardi 27 avril 2010 07:51
  • Bonjour,

    Voici une proposition pour une lecture asynchrone dans votre cas, c'est pas trop "lourd" à mettre en oeuvre... :

    ProcessStartInfo info;
    
    info = new ProcessStartInfo();
    info.RedirectStandardError = true;
    info.UseShellExecute = false;
    // Compléter info
    
    using (Process p = Process.Start(info))
    {
      Thread t;
      StringBuilder sb;
    
      sb = new StringBuilder();
    
      t = new Thread(() =>
      {
        string s;
    
        s = p.StandardError.ReadLine();
        while (s != null)
        {
          sb.AppendLine();
        }
      }).Start();
    
      p.WaitForExit(3000);
    
      if (t.IsAlive == true)
      {
        t.Abort();
      }
    
      string donnéeRécupérée = sb.ToString();        
    }

    Cordialement

     


    Gilles TOURREAU - MVP C# - Architecte .NET/Consultant/Formateur
    mardi 27 avril 2010 11:28
    Modérateur
  • Merci pour la proposition qui si j'ai bien compris réalise la lecture de la sortie dans un thread séparé, une fois le processus terminé (WaitForExit passé) on vérifie l'état du thread d'écoute et s'il est toujours actif on l'arrête.
    Par curiosité j'ai aussi fait un petit test en gérant l'évènement "OutputDataReceived".

    Je comprends bien la démarche mais cela impliquerait d'autres modification et d'autres tests et là le temps commence à me manquer surtout que l'application est déjà multithreadée.

    Je pense que je vais quand même rester sur ce quoi j'étais parti, puisque maintenant l'implémentation est correcte :

     Dim errMsg As String
    
    
     Using convProcess As New Process
     With convProcess
    
     .StartInfo.FileName = "C:\toto.exe"
    
     .StartInfo.Arguments = "ligne de commande"
     .StartInfo.CreateNoWindow = False
     .StartInfo.WindowStyle = ProcessWindowStyle.Hidden
     .StartInfo.UseShellExecute = False
     .StartInfo.RedirectStandardError = True
     .StartInfo.RedirectStandardOutput = False
    
     .Start()
     errMsg = .StandardError.ReadToEnd
    
     .WaitForExit(3000)
     If Not .HasExited Then
    
     .Kill()
     .WaitForExit()
     .Close()
     Throw New Exception("délai dépassé")
    
     ElseIf convProcess.ExitCode <> 0 Then
    
     .Close()
     Throw New Exception(errMsg)
     End If
     .Close 
     End With
     End Using

    Je vais laisser le WaitForExit et le test du HasExited même s'ils sont normalement purement "décoratif"...
    Je n'arrive pas à ma faire à l'idée de l'enlever. ;)

    Cordialement
    Mael

    mardi 27 avril 2010 12:02