2009-06-23 10 views
5

Estoy generando una aplicación de consola externa y uso la redirección de salida asíncrona.
as shown in this SO postC#: Salida de aplicación de consola de redireccionamiento: ¿cómo purgar la salida?

Mi problema es que parece que el proceso generado necesita producir cierta cantidad de la producción antes de que llegue la notificación de eventos OutputDataReceived.

Deseo recibir el evento OutputDataReceived tan pronto como sea posible.

Tengo una aplicación de redirección escueta, y aquí hay algunas observaciones:
1. Cuando llamo a un simple 'while (true) print ("X");' aplicación de consola (C#) Recibo el evento de salida inmediatamente. 2. Cuando llamo a una aplicación 3d party, estoy tratando de ajustar desde la línea de comando Veo la salida línea por línea.
3. Cuando llamo a esa aplicación de fiesta en 3D desde mi envoltorio de bare-bone (vea 1) - la salida viene en pedazos (aproximadamente un tamaño de página).

¿Qué ocurre dentro de esa aplicación?

FYI: La aplicación en cuestión es un "USBee DX Data Exctarctor (Async bus) v1.0".

Respuesta

8

Investigué un poco más y tengo una solución para la clase de proceso microsofts. Pero como mi última respuesta fue eliminada sin una razón, tuve que crear una nueva.

Así que toma este ejemplo ...

Crear una aplicación de Windows y pegar un rico cuadro de texto en el formulario principal, a continuación, añadir esto a la carga del formulario ...

 Process p = new Process() 
     { 
      StartInfo = new ProcessStartInfo() 
      { 
       FileName = "cmd.exe", 
       CreateNoWindow = true, 
       UseShellExecute = false, 
       ErrorDialog = false, 
       RedirectStandardInput = true, 
       RedirectStandardOutput = true, 
       RedirectStandardError = true, 
      }, 
      EnableRaisingEvents = true, 
      SynchronizingObject = this 
     }; 

     p.OutputDataReceived += (s, ea) => this.richTextBox1.AppendText(ea.Data); 

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

Esta es la salida algo como este ...

Microsoft Windows [Version 6.1.7601] 
Copyright (c) 2009 Microsoft Corporation. All rights reserved. 

El evento OutputDataReceived no se activa para la última línea. Después de algunos ILSpying, parece que esto es deliberado porque la última línea no termina con un crlf, supone que hay más comming y lo agrega al inicio del próximo evento.

Para corregir esto, he escrito un contenedor para la clase Process y he sacado algunas de las clases internas necesarias para que todo funcione correctamente. Aquí está la clase FixedProcess ...

using System; 
using System.Collections; 
using System.IO; 
using System.Text; 
using System.Threading; 

namespace System.Diagnostics 
{ 
    internal delegate void UserCallBack(string data); 
    public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e); 

    public class FixedProcess : Process 
    { 
     internal AsyncStreamReader output; 
     internal AsyncStreamReader error; 
     public event DataReceivedEventHandler OutputDataReceived; 
     public event DataReceivedEventHandler ErrorDataReceived; 

     public new void BeginOutputReadLine() 
     { 
      Stream baseStream = StandardOutput.BaseStream; 
      this.output = new AsyncStreamReader(this, baseStream, new UserCallBack(this.FixedOutputReadNotifyUser), StandardOutput.CurrentEncoding); 
      this.output.BeginReadLine(); 
     } 

     public void BeginErrorReadLine() 
     { 
      Stream baseStream = StandardError.BaseStream; 
      this.error = new AsyncStreamReader(this, baseStream, new UserCallBack(this.FixedErrorReadNotifyUser), StandardError.CurrentEncoding); 
      this.error.BeginReadLine(); 
     } 

     internal void FixedOutputReadNotifyUser(string data) 
     { 
      DataReceivedEventHandler outputDataReceived = this.OutputDataReceived; 
      if (outputDataReceived != null) 
      { 
       DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data); 
       if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired) 
       { 
        this.SynchronizingObject.Invoke(outputDataReceived, new object[] 
        { 
         this, 
         dataReceivedEventArgs 
        }); 
        return; 
       } 
       outputDataReceived(this, dataReceivedEventArgs); 
      } 
     } 

     internal void FixedErrorReadNotifyUser(string data) 
     { 
      DataReceivedEventHandler errorDataReceived = this.ErrorDataReceived; 
      if (errorDataReceived != null) 
      { 
       DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data); 
       if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired) 
       { 
        this.SynchronizingObject.Invoke(errorDataReceived, new object[] 
        { 
         this, 
         dataReceivedEventArgs 
        }); 
        return; 
       } 
       errorDataReceived(this, dataReceivedEventArgs); 
      } 
     } 
    } 

    internal class AsyncStreamReader : IDisposable 
    { 
     internal const int DefaultBufferSize = 1024; 
     private const int MinBufferSize = 128; 
     private Stream stream; 
     private Encoding encoding; 
     private Decoder decoder; 
     private byte[] byteBuffer; 
     private char[] charBuffer; 
     private int _maxCharsPerBuffer; 
     private Process process; 
     private UserCallBack userCallBack; 
     private bool cancelOperation; 
     private ManualResetEvent eofEvent; 
     private Queue messageQueue; 
     private StringBuilder sb; 
     private bool bLastCarriageReturn; 
     public virtual Encoding CurrentEncoding 
     { 
      get 
      { 
       return this.encoding; 
      } 
     } 
     public virtual Stream BaseStream 
     { 
      get 
      { 
       return this.stream; 
      } 
     } 
     internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding) 
      : this(process, stream, callback, encoding, 1024) 
     { 
     } 
     internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) 
     { 
      this.Init(process, stream, callback, encoding, bufferSize); 
      this.messageQueue = new Queue(); 
     } 
     private void Init(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) 
     { 
      this.process = process; 
      this.stream = stream; 
      this.encoding = encoding; 
      this.userCallBack = callback; 
      this.decoder = encoding.GetDecoder(); 
      if (bufferSize < 128) 
      { 
       bufferSize = 128; 
      } 
      this.byteBuffer = new byte[bufferSize]; 
      this._maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize); 
      this.charBuffer = new char[this._maxCharsPerBuffer]; 
      this.cancelOperation = false; 
      this.eofEvent = new ManualResetEvent(false); 
      this.sb = null; 
      this.bLastCarriageReturn = false; 
     } 
     public virtual void Close() 
     { 
      this.Dispose(true); 
     } 
     void IDisposable.Dispose() 
     { 
      this.Dispose(true); 
      GC.SuppressFinalize(this); 
     } 
     protected virtual void Dispose(bool disposing) 
     { 
      if (disposing && this.stream != null) 
      { 
       this.stream.Close(); 
      } 
      if (this.stream != null) 
      { 
       this.stream = null; 
       this.encoding = null; 
       this.decoder = null; 
       this.byteBuffer = null; 
       this.charBuffer = null; 
      } 
      if (this.eofEvent != null) 
      { 
       this.eofEvent.Close(); 
       this.eofEvent = null; 
      } 
     } 
     internal void BeginReadLine() 
     { 
      if (this.cancelOperation) 
      { 
       this.cancelOperation = false; 
      } 
      if (this.sb == null) 
      { 
       this.sb = new StringBuilder(1024); 
       this.stream.BeginRead(this.byteBuffer, 0, this.byteBuffer.Length, new AsyncCallback(this.ReadBuffer), null); 
       return; 
      } 
      this.FlushMessageQueue(); 
     } 
     internal void CancelOperation() 
     { 
      this.cancelOperation = true; 
     } 
     private void ReadBuffer(IAsyncResult ar) 
     { 
      int num; 
      try 
      { 
       num = this.stream.EndRead(ar); 
      } 
      catch (IOException) 
      { 
       num = 0; 
      } 
      catch (OperationCanceledException) 
      { 
       num = 0; 
      } 
      if (num == 0) 
      { 
       lock (this.messageQueue) 
       { 
        if (this.sb.Length != 0) 
        { 
         this.messageQueue.Enqueue(this.sb.ToString()); 
         this.sb.Length = 0; 
        } 
        this.messageQueue.Enqueue(null); 
       } 
       try 
       { 
        this.FlushMessageQueue(); 
        return; 
       } 
       finally 
       { 
        this.eofEvent.Set(); 
       } 
      } 
      int chars = this.decoder.GetChars(this.byteBuffer, 0, num, this.charBuffer, 0); 
      this.sb.Append(this.charBuffer, 0, chars); 
      this.GetLinesFromStringBuilder(); 
      this.stream.BeginRead(this.byteBuffer, 0, this.byteBuffer.Length, new AsyncCallback(this.ReadBuffer), null); 
     } 
     private void GetLinesFromStringBuilder() 
     { 
      int i = 0; 
      int num = 0; 
      int length = this.sb.Length; 
      if (this.bLastCarriageReturn && length > 0 && this.sb[0] == '\n') 
      { 
       i = 1; 
       num = 1; 
       this.bLastCarriageReturn = false; 
      } 
      while (i < length) 
     { 
      char c = this.sb[i]; 
      if (c == '\r' || c == '\n') 
      { 
       if (c == '\r' && i + 1 < length && this.sb[i + 1] == '\n') 
       { 
        i++; 
       } 

       string obj = this.sb.ToString(num, i + 1 - num); 

       num = i + 1; 

       lock (this.messageQueue) 
       { 
        this.messageQueue.Enqueue(obj); 
       } 
      } 
      i++; 
     } 

      // Flush Fix: Send Whatever is left in the buffer 
      string endOfBuffer = this.sb.ToString(num, length - num); 
      lock (this.messageQueue) 
      { 
       this.messageQueue.Enqueue(endOfBuffer); 
       num = length; 
      } 
      // End Flush Fix 

      if (this.sb[length - 1] == '\r') 
      { 
       this.bLastCarriageReturn = true; 
      } 
      if (num < length) 
      { 
       this.sb.Remove(0, num); 
      } 
      else 
      { 
       this.sb.Length = 0; 
      } 
      this.FlushMessageQueue(); 
     } 
     private void FlushMessageQueue() 
     { 
      while (this.messageQueue.Count > 0) 
      { 
       lock (this.messageQueue) 
       { 
        if (this.messageQueue.Count > 0) 
        { 
         string data = (string)this.messageQueue.Dequeue(); 
         if (!this.cancelOperation) 
         { 
          this.userCallBack(data); 
         } 
        } 
        continue; 
       } 
       break; 
      } 
     } 
     internal void WaitUtilEOF() 
     { 
      if (this.eofEvent != null) 
      { 
       this.eofEvent.WaitOne(); 
       this.eofEvent.Close(); 
       this.eofEvent = null; 
      } 
     } 
    } 

    public class DataReceivedEventArgs : EventArgs 
    { 
     internal string _data; 
     /// <summary>Gets the line of characters that was written to a redirected <see cref="T:System.Diagnostics.Process" /> output stream.</summary> 
     /// <returns>The line that was written by an associated <see cref="T:System.Diagnostics.Process" /> to its redirected <see cref="P:System.Diagnostics.Process.StandardOutput" /> or <see cref="P:System.Diagnostics.Process.StandardError" /> stream.</returns> 
     /// <filterpriority>2</filterpriority> 
     public string Data 
     { 
      get 
      { 
       return this._data; 
      } 
     } 
     internal DataReceivedEventArgs(string data) 
     { 
      this._data = data; 
     } 
    } 
} 

Pegue eso en su proyecto y luego cambie ...

Process p = new Process() 
{ 
    .... 

a

FixedProcess p = new FixedProcess() 
{ 
    .... 

Ahora la aplicación debe mostrar algo como esto ...

Microsoft Windows [Version 6.1.7601] 

Copyright (c) 2009 Microsoft Corporation. All rights reserved. 

C:\Projects\FixedProcess\bin\Debug> 

sin necesidad de hacer ningún otro cambio en su código existente. También sigue siendo asincrónico y envuelto muy bien. La única advertencia es que ahora obtendrá múltiples eventos para grandes volúmenes de producción con interrupciones potenciales intermedias, por lo que tendrá que manejar este escenario usted mismo. Aparte de eso, debería ser todo bien.

+0

"La única advertencia es que ahora obtendrá múltiples eventos para una gran producción con posibles interrupciones intermedias" - ¿Cómo sería eso exactamente un problema? Acabo de agregar un 'StringBuilder.Append()' al evento 'OutputDataReceived'. – Nyerguds

+0

Este método ocasionalmente me da una salida vacía en las ejecuciones cortas del programa, por cierto ... ¿hay alguna manera de esperar que el proceso asíncrono se ponga al día por completo antes de procesar el valor final? – Nyerguds

+0

En esa nota, ¿no debería esto también anular el evento 'Salir' para esperar las operaciones asincrónicas? – Nyerguds

1

Parece que el problema fue que la aplicación ficticia se escribió en C# que vacía la salida automáticamente una vez cada impresión mientras que la aplicación de terceros se escribió en c/C++ y por lo tanto solo se escribió cuando el stdoutbuffer estaba lleno. La única solución que he encontrado es asegurarse de que la aplicación c/C++ se vacíe después de cada impresión o establecer su memoria intermedia en 0.

Cuestiones relacionadas