Questo sito utilizza cookies, anche di terze parti, per mostrare pubblicità e servizi in linea con il tuo account. Leggi l'informativa sui cookies.
Username: Password: oppure
C# / VB.NET - [VB.NET]Problemi nel leggere stdout e stderr di un processo lanciato con System.Diagnostics.Process
Forum - C# / VB.NET - [VB.NET]Problemi nel leggere stdout e stderr di un processo lanciato con System.Diagnostics.Process

Avatar
GN (Member)
Guru


Messaggi: 770
Iscritto: 30/04/2011

Segnala al moderatore
Postato alle 11:49
Giovedì, 11/09/2014
Salve a tutti, ho questo problema in un'applicazione .NET. Ho bisogno di catturare l'output di un processo, sia ciò che viene scritto sullo standard output che sullo standard error; per ora, in particolare, sono interessato all'output del compilatore java (javac.exe, che in realtà scrive tutto su stderr, ma vorrei una soluzione "pulita" in modo che in futuro possa riutilizzare il codice per altri comandi che mandano alcuni messaggi di output su stdout e altri su stderr). In altre parole, ho bisogno di leggere in tempo reale stdout e stderr man mano che vengono scritti dal processo e nell'ordine giusto (ovvero non prima tutto stdout e poi tutto stderr o viceversa), esattamente come vengono mostrati quando il processo viene lanciato da terminale.

Detto questo, il codice che sono riuscito a scrivere è questo.
Codice sorgente - presumibilmente VB.NET

  1. Delegate Sub onOutputDelegate(ByVal output As String, ByVal isError As Boolean) 'funzione che verrà chiamata ogni volta che il processo scrive qualcosa in uno dei due flussi di output
  2.  
  3.     Private Sub runCommand(ByVal cmd As String, ByVal onOutput As onOutputDelegate, Optional ByVal cwd As String = "")
  4.         Dim p As New Process()
  5.         p.StartInfo.FileName = cmd
  6.         p.StartInfo.UseShellExecute = False
  7.         p.StartInfo.CreateNoWindow = True
  8.         p.StartInfo.RedirectStandardOutput = True
  9.         p.StartInfo.RedirectStandardError = True
  10.         If cwd <> "" Then p.StartInfo.WorkingDirectory = cwd
  11.         p.Start()
  12.         Dim thOut As New Thread(Sub()
  13.                                     While Not p.HasExited
  14.                                         onOutput(p.StandardOutput.ReadLine(), False)
  15.                                     End While
  16.                                 End Sub)
  17.         Dim thErr As New Thread(Sub()
  18.                                     While Not p.HasExited
  19.                                         onOutput(p.StandardError.ReadLine(), True)
  20.                                     End While
  21.                                 End Sub)
  22.         thOut.Start()
  23.         thErr.Start()
  24.         p.WaitForExit()
  25.     End Sub


Come potete vedere la soluzione che mi è venuta in mente è di lanciare contemporaneamente due thread separati, uno per stdout e uno per stderr.
Inizialmente sembrava funzionare tutto, ma poi mi sono accorto di un comportamento strano: a volte certe righe di output non vengono lette, mente altre volte si (lanciando lo stesso identico comando con gli stessi parametri), in maniera apparentemente casuale.
Qualcuno saprebbe aiutarmi? Inoltre, secondo voi la soluzione dei due thread vi sembra una buona idea o ci sono alternative migliori? Grazie in anticipo a chiunque risponderà ;)

PS: se qualcuno fosse interessato a vedere l'intero file di codice lo trovate qui: https://sourceforge.net/p/universalide/java-addon-code/ci/m ...

Ultima modifica effettuata da GN il 11/09/2014 alle 11:51


PM Quote
Avatar
vankraster (Member)
Rookie


Messaggi: 32
Iscritto: 05/11/2010

Segnala al moderatore
Postato alle 11:54
Venerdì, 12/09/2014
Io sono riuscito con questo codice:
Codice sorgente - presumibilmente C# / VB.NET

  1. string q = "";
  2.             using (Process p = new Process())
  3.             {
  4.                 p.StartInfo.RedirectStandardOutput = true;
  5.                 p.StartInfo.UseShellExecute = false;
  6.                 p.StartInfo.CreateNoWindow = true;
  7.                 p.StartInfo.FileName = @"c:\temp\test.bat";
  8.  
  9.                 p.Start();
  10.  
  11.                 while (!p.HasExited)
  12.                     q += p.StandardOutput.ReadToEnd();
  13.             }
  14.  
  15.  
  16.             textBox1.Text = q;




eseguo un file bat che ho fatto io tipo: echo 'prova'  ma questo non conta ...
su textBox1 mi da il risultato.


Ci possono togliere tutto tranne la ragione e con questa possiamo ricostruire l'universo.
PM Quote
Avatar
GN (Member)
Guru


Messaggi: 770
Iscritto: 30/04/2011

Segnala al moderatore
Postato alle 23:05
Venerdì, 12/09/2014
Il probelma è che così leggi solo lo Standard Output, io invece, come avevo specificato, ho bisogno di leggere contemporaneamente Standard Output e Standard Error... Grazie comunque


PM Quote
Avatar
vankraster (Member)
Rookie


Messaggi: 32
Iscritto: 05/11/2010

Segnala al moderatore
Postato alle 9:22
Lunedì, 15/09/2014
Nel ciclo while potresti aggiungere

Codice sorgente - presumibilmente Plain Text

  1. q + = p.StandardError.ReadToEnd();



Perché ti serve leggere i dati contemporaneamente? Forse troviamo un'altra soluzione.


Ci possono togliere tutto tranne la ragione e con questa possiamo ricostruire l'universo.
PM Quote
Avatar
GN (Member)
Guru


Messaggi: 770
Iscritto: 30/04/2011

Segnala al moderatore
Postato alle 18:43
Giovedì, 18/09/2014
Testo quotato

Postato originariamente da vankraster:
Perché ti serve leggere i dati contemporaneamente? Forse troviamo un'altra soluzione.


Ti spiego, sto programmando un IDE che deve lanciare dei compilatori e mostrarne e parsarne l'output (in modo da mostrare una lista di errori presenti nel codice), attualmente sono al lavoro con il compilatore java ma vorrei generalizzare il codice per l'avvio di un processo e la lettura del rispettivo output in modo da poterlo riutilizzare se e quando ne supporterò altri; vorrei quindi riuscire a ricevere sia l'stdout che l'stderr in modo che su qualsiasi di questi flussi scriva il programma che lancio, l'output venga mostrato all'utente nel mio programma.
Usando ReadToEnd ho parzialmente risolto in questo modo, dovendo però rinunciare ella lettura in tempo reale:
Codice sorgente - presumibilmente VB.NET

  1. p.Start()
  2.         p.WaitForExit()
  3.         Dim out As String = p.StandardOutput.ReadToEnd()
  4.         For Each l As String In out.Split(Environment.NewLine)
  5.             onOutput(l, False)
  6.         Next
  7.         Dim err As String = p.StandardError.ReadToEnd()
  8.         For Each l As String In err.Split(Environment.NewLine)
  9.             onOutput(l, True)
  10.         Next


E così funziona, anche se ovviamente l'output arriva solo quando il processo è terminato e non in ordine (prima tutto stdout e poi tutto stderr). Possibile che non sia possibile monitorare entrambi gli stream e leggerli riga per riga man mano che il processo li scrive? E' una settimana ormai che ci sto sbattendo la testa :om: :pat:

P.S. ho provato anche con gli eventi OutputDataReceived e ErrorDataReceived, in questo modo:
Codice sorgente - presumibilmente VB.NET

  1. AddHandler p.OutputDataReceived, Sub(sender As Object, e As DataReceivedEventArgs)
  2.                                              onOutput(e.Data, False)
  3.                                          End Sub
  4.         p.BeginOutputReadLine()
  5.         AddHandler p.ErrorDataReceived, Sub(sender As Object, e As DataReceivedEventArgs)
  6.                                             onOutput(e.Data, True)
  7.                                         End Sub
  8.         p.BeginOutputReadLine()
  9.         p.BeginErrorReadLine()


Ma funziona solo su un flusso alla volta.


PM Quote