65.9K
CodeProject is changing. Read more.
Home

Launching a process and processing its standard output in real-time

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.63/5 (10 votes)

May 30, 2006

2 min read

viewsIcon

47399

downloadIcon

773

A patch for an article by Mike Mayer.

Introduction

When working in a project to burn a CD automatically from a backup directory, I had to evaluate the output of a console program.

Google helped me to find some articles doing similar work. The best I found was by Mike Mayer and published here: Launching a process and displaying its standard output. The article is great and got my 5!

I used Mike's ProcessCaller and AsyncOperation classes. But there were some real-time issues which forced me to make some source code changes which I want to publish here. (That's the reason why my article title is very similar to Mike's title.)

Real-time Issues:

In the StdOutReceived event handler of my application, besides writing them into a TextBox, I collected all input lines in a StringBuilder, and in the Completed event handler, I wanted to evaluate the contents of the StringBuilder. But is was empty! To find the reason, in the Completed event handler, I wrote a "*** Process Done ***" to the TextBox.

The output was:

As to be seen, the Completed event overhauled all the StdOutReceived events.

I fixed this in this way:

In Mike's ProcessCaller class:

  • I added the properties "public bool EDone;" and "public bool SDone;"
  • I added an internal StringBuilder and a method to add content to the StringBuilder.
  • I added the property "public string Type;" to the DataReceivedEventArgs class.
  • I changed the ReadStdOut method to:
    protected virtual void ReadStdOut()
    {
      string str;
      while ((str = process.StandardOutput.ReadLine()) != null)
      {      
        FireAsync(StdOutReceived, this, new DataReceivedEventArgs("S", str));
      }
      FireAsync(StdOutReceived, this, new DataReceivedEventArgs("S", null));
    }
  • and the ReadStdErr method accordingly (with the type "E").
  • In my application, I changed the StdOutReceived event handler to:
    Private Sub callerRead(ByVal sender As Object, _
                ByVal e As DataReceivedEventArgs)
      Dim processCaller As ProcessCaller = CType(sender, ProcessCaller)
      ' text is null when end of read appeared in processCaller
      If IsNothing(e.Text) Then
        Select Case e.Type
          Case "S"
            ' store in processCaller that standard output has finished
            processCaller.SDone = True
          Case "E"
            ' store in processCaller that error output has finished
            processCaller.EDone = True
        End Select        Me.txtLog.AppendText(e.Type & " Finished")
        ' if both are finished, everything is done.
        ' I can evaluate etc.
        If processCaller.SDone And processCaller.EDone Then
            Evaluate(processCaller)
      Else
        Dim tmp As String = e.Text & Environment.NewLine
        ' append line to StringBuilder in processCaller
        processCaller.AddOutput(tmp)
        Me.txtLog.AppendText(e.Type & " " & tmp)
      End If
    End Sub

This gave the result:

As to be seen, when the error output is completed, and at the very end, the standard output is done. So my evaluation processing here has the complete data.

There was one issue left: The "E" lines and "S" lines are not mixed. They depend on the order of invoking in ProcessCaller:

new MethodInvoker(ReadStdErr).BeginInvoke(null, null);
new MethodInvoker(ReadStdOut).BeginInvoke(null, null);

So to reach a 99% "real-time" processing, I interrupted the ReadStdOut and ReadStdErr method threads, so that the method (and the ReadStdErr method accordingly) finally looks like:

protected virtual void ReadStdOut()
{
  string str;
  while ((str = process.StandardOutput.ReadLine()) != null)
  {      
    FireAsync(StdOutReceived, this, 
              new DataReceivedEventArgs("S", str));
    // sleep for a very short time
    // to give ReadStdErr the ability to work
    Thread.Sleep(1); 
  }
  FireAsync(StdOutReceived, this, 
            new DataReceivedEventArgs("S", null));
}

Points of Interest

Would be nice if Mike changes his article accordingly!