Tuesday, March 27, 2012

Reading redirected output of the process: limitations and how to work around

During last month I was working on a project built on top of the Console-windows. An interesting problem I was facing is the read of redirected output (asyncronous) from that process. It seems to be quite easy to handle such a task, and it is, unless you need to read "all the output".
Well, the simple approach of handling such a task is like the following:
1. Define a ProcessStartInfo with redirected output:
    ProcessStartInfo tmpStartInfo = new ProcessStartInfo()
    {
        FileName = "consoleApp.exe",
        UseShellExecute = false,
        RedirectStandardOutput = true
    }
2. Create a process and set the start info to alrelady defined one:
    ProcessStartInfo tmpProcess = new Process()
    tmpProcess.StartInfo = tmpStartInfo;
3. Define and register a handler for the OutputDataReceived event:
    private static void Process_OutputDataReceived(object sendingProcess, DataReceivedEventArgs outLine)
    {
        Console.WriteLine(outLine);
    }
    tmpProcess.OutputDataReceived += new DataReceivedEventHandler (Process_OutputDataReceived);

4. Start the process.
    tmpProcess.Start();

Yes, it's simple, but there is an open issue left in here. The process won't redirect the output, until there is a new-line character in the stream, which means, that the Process will fire the OutputDataReceived event only as soon as a new line is available on the stream. So for me this was a big limitation, because if the process is writing something on a line and still waits for some input from the user on the same line, we will never receive that line, and won't even know that the process is waiting for user input there.
To fix this we need to observe the stream on our own, not awaiting the event to fire. So, here is the algorithm I came up with, to handle the behavior described:
   
private void ObserveStreamInBackground()
{
  try
  {
    char? tmpStreamChar = null;
    do
    {
      int tmpStatus = this.Stream.Peek();
      if (tmpStatus == -1)
      {
        if (this.messageBuffer.Length > 0)
        {
          this.InformMessage();
        }
        tmpStreamChar = (char)this.Stream.Read();
      }
      char tmpCurrentChar = tmpStreamChar ?? (char)this.Stream.Read();
      if (tmpStreamChar.HasValue)
      {
        tmpStreamChar = null;
      }
      this.messageBuffer.Append(tmpCurrentChar);
      if (tmpCurrentChar == '\n')
      {
        this.InformMessage();
      }
    } while (true);
   }
  catch (ThreadAbortException ex)
  {
  }
}

private void InformMessage()
{
  string tmpMessage = this.messageBuffer.ToString();
  this.messageBuffer.Clear();
  this.lastMessage = tmpMessage;
  this.lastMessageReceivedAt = DateTime.Now;
  this.OnMessageReceived(tmpMessage);
}

The InformMessage() method is there to just clear the current message buffer and fire the event with the message data in it.
That's all. Experiment with it and give me your feedback. Good Luck !

1 comment:

Thomas McCormick said...

Your place is valuable for me. Thanks!…