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)
If IsNothing(e.Text) Then
Select Case e.Type
Case "S"
processCaller.SDone = True
Case "E"
processCaller.EDone = True
End Select Me.txtLog.AppendText(e.Type & " Finished")
If processCaller.SDone And processCaller.EDone Then
Evaluate(processCaller)
Else
Dim tmp As String = e.Text & Environment.NewLine
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));
Thread.Sleep(1);
}
FireAsync(StdOutReceived, this,
new DataReceivedEventArgs("S", null));
}
Points of Interest
Would be nice if Mike changes his article accordingly!