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






3.63/5 (10 votes)
May 30, 2006
2 min read

47399

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 theStringBuilder
. - I added the property "
public string Type;
" to theDataReceivedEventArgs
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!