2012-01-10 10 views
17

Ho un problema nel mio progetto. Vorrei avviare un processo, 7z.exe (versione console). che ho provato tre cose diverse:Ottieni output in uscita dal processo

  • Process.StandardOutput.ReadToEnd();
  • OutputDataReceived & BeginOutputReadLine
  • StreamWriter

Niente funziona. "Aspetta sempre" per la fine del processo per mostrare ciò che voglio. Non ho alcun codice da inserire, solo se vuoi il mio codice con una delle cose elencate qui sopra. Grazie.

Edit: Il mio codice:

 process.StartInfo.UseShellExecute = false; 
     process.StartInfo.RedirectStandardOutput = true; 
     process.StartInfo.CreateNoWindow = true; 
     process.Start(); 

     this.sr = process.StandardOutput; 
     while (!sr.EndOfStream) 
     { 
      String s = sr.ReadLine(); 
      if (s != "") 
      { 
       System.Console.WriteLine(DateTime.Now + " - " + s); 
      } 
     } 

O

process.StartInfo.UseShellExecute = false; 
process.StartInfo.RedirectStandardOutput = true; 
process.OutputDataReceived += new DataReceivedEventHandler(recieve); 
process.StartInfo.CreateNoWindow = true; 
process.Start(); 
process.BeginOutputReadLine(); 
process.WaitForExit(); 
public void recieve(object e, DataReceivedEventArgs outLine) 
{ 
    System.Console.WriteLine(DateTime.Now + " - " + outLine.Data); 
} 

O

process.StartInfo.UseShellExecute = false; 
process.StartInfo.RedirectStandardOutput = true; 
process.Start(); 
string output = p.StandardOutput.ReadToEnd(); 
process.WaitForExit(); 

Dove "processo" è il mio processo di pre-made

Ok lo so perchè questo non funziona correttamente: 7z.exe è un bug: mostra una percentuale di caricamento in console e invia informazioni solo quando il file corrente è finito. Ad esempio, nell'estrazione funziona bene :). Cercherò un altro modo per utilizzare le funzioni 7z senza 7z.exe (magari con 7za.exe o con qualche DLL). Grazie a tutti. Per rispondere alla domanda, l'evento OuputDataReceeved funziona correttamente!

+0

qualsiasi motivo non si utilizza la DLL/SDK downloadabe da 7zip che permette un controllo molto maggiore di qualsiasi tecnica basata su console? – Yahia

+0

Sarebbe utile vedere il codice che hai provato con Process ad esempio dove stai creando il processo – MethodMan

+0

Perché 7z.exe copre tutte le funzioni che volevo. – Extaze

risposta

16

dare un'occhiata a questa pagina, sembra questa è la soluzione per voi : http://msdn.microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspx e http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

[Edit] Questo è un esempio di lavoro:

 Process p = new Process(); 
     p.StartInfo.RedirectStandardError = true; 
     p.StartInfo.RedirectStandardOutput = true; 
     p.StartInfo.UseShellExecute = false; 
     p.StartInfo.CreateNoWindow = true; 
     p.StartInfo.FileName = @"C:\Program Files (x86)\gnuwin32\bin\ls.exe"; 
     p.StartInfo.Arguments = "-R C:\\"; 

     p.OutputDataReceived += new DataReceivedEventHandler(
      (s, e) => 
      { 
       Console.WriteLine(e.Data); 
      } 
     ); 
     p.ErrorDataReceived += new DataReceivedEventHandler((s, e) => { Console.WriteLine(e.Data); }); 

     p.Start(); 
     p.BeginOutputReadLine(); 

Btw, ls -R C: \ elenca tutti i file dalla radice di C: in modo ricorsivo. Questi sono molti file, e sono sicuro che non è fatto quando i primi risultati appaiono sullo schermo. C'è una possibilità che 7zip trattiene l'output prima di mostrarlo. Non sono sicuro di quali parametri si danno al processo.

+0

. Guarda il mio codice:/ – Extaze

+0

Tutto quello che posso trovare è che dovresti usare l'evento OutputDataReceived, al momento non ho tempo per testarlo, forse più tardi. (http://harbertc.wordpress.com/2006/05/16/reading-text-from-a-process-executed-programmatically-in-c/) –

+0

Ho provato di tutto, ma il problema era 7z.exe. Ho deciso di prendere una libreria 7z per C# (SevenZipSharp) e il problema è stato risolto. Grazie;) – Extaze

0

Prova questo.

 Process notePad = new Process(); 

     notePad.StartInfo.FileName = "7z.exe"; 
     notePad.StartInfo.RedirectStandardOutput = true; 
     notePad.StartInfo.UseShellExecute = false; 

     notePad.Start(); 
     StreamReader s = notePad.StandardOutput; 



     String output= s.ReadToEnd(); 


     notePad.WaitForExit(); 

Let quanto sopra in una thread.

Ora per l'aggiornamento l'uscita per interfaccia utente, è possibile utilizzare un timer con due linee

Console.Clear(); 
    Console.WriteLine(output); 

Questo può aiutare

+1

Per quanto posso vedere, questo codice attenderà anche il completamento. Come affermato nella domanda, non vuole aspettare finché non è finito, ma vuole la produzione live –

+0

Come ha detto Michiel, cioè ottenere l'output "finale" – Extaze

1

Ho usato la classe CmdProcessor descritta here su diversi progetti con molto successo. All'inizio sembra un po 'scoraggiante, ma è molto facile da usare.

+0

Non funziona ... Attende che il processo si fermi per mostrarmi informazioni ... – Extaze

4

Non so se qualcuno è ancora alla ricerca di una soluzione a questo, ma è venuto fuori più volte per me perché sto scrivendo uno strumento in Unity a supporto di alcuni giochi e grazie alla limitata interoperabilità di alcuni sistemi con mono (ad esempio PIA per leggere il testo da Word, ad esempio), spesso devo scrivere eseguibili specifici del sistema operativo (a volte Windows, a volte MacOS) e lanciarli da Process.Start().

Il problema è che quando si avvia un eseguibile come questo, verrà attivato in un altro thread che blocca l'app principale, causando un blocco. Se desideri fornire un feedback utile ai tuoi utenti durante questo periodo al di là delle icone rotanti evocate dal tuo rispettivo sistema operativo, allora sei un po 'fregato. L'utilizzo di uno stream non funzionerà perché il thread è ancora bloccato fino al termine dell'esecuzione.

La soluzione su cui mi sono imbattuto, che potrebbe sembrare estrema per alcune persone ma trovo che funziona abbastanza bene per me, è utilizzare socket e multithreading per configurare comunicazioni sincrone affidabili tra le due app. Naturalmente, questo funziona solo se si stanno creando entrambe le app. Altrimenti, penso che tu non abbia fortuna. ... Mi piacerebbe vedere se funziona con il multithreading usando un approccio stream tradizionale, quindi se qualcuno vorrebbe provarlo e pubblicare i risultati qui sarebbe fantastico.

Ad ogni modo, ecco la soluzione attualmente lavora per me:

Nel l'applicazione principale, o chiamando, faccio qualcosa di simile:

/// <summary> 
/// Handles the OK button click. 
/// </summary> 
private void HandleOKButtonClick() { 
string executableFolder = ""; 

#if UNITY_EDITOR 
executableFolder = Path.Combine(Application.dataPath, "../../../../build/Include/Executables"); 
#else 
executableFolder = Path.Combine(Application.dataPath, "Include/Executables"); 
#endif 

EstablishSocketServer(); 

var proc = new Process { 
    StartInfo = new ProcessStartInfo { 
     FileName = Path.Combine(executableFolder, "WordConverter.exe"), 
     Arguments = locationField.value + " " + _ipAddress.ToString() + " " + SOCKET_PORT.ToString(), 
     UseShellExecute = false, 
     RedirectStandardOutput = true, 
     CreateNoWindow = true 
    } 
}; 

proc.Start(); 

Ecco dove stabilisco il server socket:

/// <summary> 
/// Establishes a socket server for communication with each chapter build script so we can get progress updates. 
/// </summary> 
private void EstablishSocketServer() { 
    //_dialog.SetMessage("Establishing socket connection for updates. \n"); 
    TearDownSocketServer(); 

    Thread currentThread; 

    _ipAddress = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0]; 
    _listener = new TcpListener(_ipAddress, SOCKET_PORT); 
    _listener.Start(); 

    UnityEngine.Debug.Log("Server mounted, listening to port " + SOCKET_PORT); 

    _builderCommThreads = new List<Thread>(); 

    for (int i = 0; i < 1; i++) { 
     currentThread = new Thread(new ThreadStart(HandleIncomingSocketMessage)); 
     _builderCommThreads.Add(currentThread); 
     currentThread.Start(); 
    } 
} 

/// <summary> 
/// Tears down socket server. 
/// </summary> 
private void TearDownSocketServer() { 
    _builderCommThreads = null; 

    _ipAddress = null; 
    _listener = null; 
} 

Ecco il mio gestore di socket per il thread ... si noti che in alcuni casi sarà necessario creare più thread; è per questo che ho che List _builderCommThreads in là (ho portato dal codice altrove dove stavo facendo qualcosa di simile, ma chiamando più istanze di fila):

/// <summary> 
/// Handles the incoming socket message. 
/// </summary> 
private void HandleIncomingSocketMessage() { 
    if (_listener == null) return; 

    while (true) { 
     Socket soc = _listener.AcceptSocket(); 
     //soc.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 10000); 
     NetworkStream s = null; 
     StreamReader sr = null; 
     StreamWriter sw = null; 
     bool reading = true; 

     if (soc == null) break; 

     UnityEngine.Debug.Log("Connected: " + soc.RemoteEndPoint); 

     try { 
      s = new NetworkStream(soc); 
      sr = new StreamReader(s, Encoding.Unicode); 
      sw = new StreamWriter(s, Encoding.Unicode); 
      sw.AutoFlush = true; // enable automatic flushing 

      while (reading == true) { 
       string line = sr.ReadLine(); 

       if (line != null) { 
        //UnityEngine.Debug.Log("SOCKET MESSAGE: " + line); 
        UnityEngine.Debug.Log(line); 

        lock (_threadLock) { 
         // Do stuff with your messages here 
        } 
       } 
      } 

      // 
     } catch (Exception e) { 
      if (s != null) s.Close(); 
      if (soc != null) soc.Close(); 
      UnityEngine.Debug.Log(e.Message); 
      //return; 
     } finally { 

     // 
     if (s != null) s.Close(); 
     if (soc != null) soc.Close(); 

     UnityEngine.Debug.Log("Disconnected: " + soc.RemoteEndPoint); 
     } 
    } 

    return; 
} 

Naturalmente, avrete bisogno di dichiarare un certo roba in alto:

private TcpListener _listener = null; 
private IPAddress _ipAddress = null; 
private List<Thread> _builderCommThreads = null; 
private System.Object _threadLock = new System.Object(); 

... poi nel file eseguibile invocato impostare l'altra estremità (io ho usato statica in questo caso, è possibile utilizzare quello che volete):

private static TcpClient _client = null; 
private static Stream _s = null; 
private static StreamReader _sr = null; 
private static StreamWriter _sw = null; 
private static string _ipAddress = ""; 
private static int _port = 0; 
private static System.Object _threadLock = new System.Object(); 

/// <summary> 
/// Main method. 
/// </summary> 
/// <param name="args"></param> 
static void Main(string[] args) { 
    try { 
     if (args.Length == 3) { 
      _ipAddress = args[1]; 
      _port = Convert.ToInt32(args[2]); 

      EstablishSocketClient(); 
     } 

     // Do stuff here 

     if (args.Length == 3) Cleanup(); 
    } catch (Exception exception) { 
     // Handle stuff here 
     if (args.Length == 3) Cleanup(); 
    } 
} 

/// <summary> 
/// Establishes the socket client. 
/// </summary> 
private static void EstablishSocketClient() { 
    _client = new TcpClient(_ipAddress, _port); 

    try { 
     _s = _client.GetStream(); 
     _sr = new StreamReader(_s, Encoding.Unicode); 
     _sw = new StreamWriter(_s, Encoding.Unicode); 
     _sw.AutoFlush = true; 
    } catch (Exception e) { 
     Cleanup(); 
    } 
} 

/// <summary> 
/// Clean up this instance. 
/// </summary> 
private static void Cleanup() { 
    _s.Close(); 
    _client.Close(); 

    _client = null; 
    _s = null; 
    _sr = null; 
    _sw = null; 
} 

/// <summary> 
/// Logs a message for output. 
/// </summary> 
/// <param name="message"></param> 
private static void Log(string message) { 
    if (_sw != null) { 
     _sw.WriteLine(message); 
    } else { 
     Console.Out.WriteLine(message); 
    } 
} 

. ..IO' m usando questo per lanciare uno strumento da riga di comando su Windows che usa le cose PIA per estrarre il testo da un documento di Word. Ho provato PIA i file .dll in Unity, ma ho incontrato problemi di interoperabilità con mono. Lo sto utilizzando anche su MacOS per invocare script di shell che lanciano istanze di Unity aggiuntive in modalità batch ed eseguono script di editor in quelle istanze che rispondono allo strumento tramite questa connessione socket. È fantastico, perché ora posso inviare feedback all'utente, eseguire il debug, monitorare e rispondere a specifici passaggi del processo, eccetera, eccetera.

HTH

4

per gestire correttamente l'uscita e/o errori di reindirizzamento si deve anche reindirizzare input. Sembra che la funzione/bug in fase di esecuzione dell'applicazione esterna stia iniziando e da quello che ho visto finora, non è menzionata da nessun'altra parte.

Esempio di utilizzo:

 Process p = new Process(...); 

     p.StartInfo.UseShellExecute = false; 
     p.StartInfo.RedirectStandardOutput = true; 
     p.StartInfo.RedirectStandardError = true; 
     p.StartInfo.RedirectStandardInput = true; // Is a MUST! 
     p.EnableRaisingEvents = true; 

     p.OutputDataReceived += OutputDataReceived; 
     p.ErrorDataReceived += ErrorDataReceived; 

     Process.Start(); 

     p.BeginOutputReadLine(); 
     p.BeginErrorReadLine(); 

     p.WaitForExit(); 

     p.OutputDataReceived -= OutputDataReceived; 
     p.ErrorDataReceived -= ErrorDataReceived; 

...

void OutputDataReceived(object sender, DataReceivedEventArgs e) 
    { 
     // Process line provided in e.Data 
    } 

    void ErrorDataReceived(object sender, DataReceivedEventArgs e) 
    { 
     // Process line provided in e.Data 
    } 
+1

Il reindirizzamento dello standard input ha risolto il problema per me. –