I have problem with reading the output of one Process asynchronously in C#. I found some other similar questions on this site but they don't really help me. Here is what I do:
It works fine but the output of the started process writes some percents(%
) which I want to get but I can't since my code reads line by line and the percents don't show up.
Example:
%0,%1...%100
Finished.
My output:
%0
Finished.
Here is the current code of my program:
StringBuilder sBuilder = new StringBuilder();
static void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
sBuilder.AppendLine(e.Data);
}
static void CommandExecutor()
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = /*path of the program*/,
Arguments = /*arguments*/,
CreateNoWindow = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
RedirectStandardOutput = true
}
};
process.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
}
Process.WaitForExit()
will wait until the asynchronous output / error stream reading finished. Unfortunately this is not true for Process.WaitForExit(timeout)
overload. This is what the Process
class does internally:
//...
finally
{
if (processWaitHandle != null)
{
processWaitHandle.Close();
}
if (this.output != null && milliseconds == -1)
{
this.output.WaitUtilEOF();
}
if (this.error != null && milliseconds == -1)
{
this.error.WaitUtilEOF();
}
this.ReleaseProcessHandle(safeProcessHandle);
}
... So it will wait for the async reads only if there was no timeout!
To fix it simply call the parameterless WaitForExit()
after WaitForExit(timeout)
returned true:
// ...
if (process.WaitForExit( 10 * 1000 ) && process.WaitForExit() )
{
// Process'es OutputDataReceived / ErrorDataReceived callbacks will not be called again, EOF streams reached
}
else
{
throw new Exception("timeout");
}
For details read the remarks here: http://msdn.microsoft.com/en-us/library/ty0d8k56%28v=vs.110%29
It seems that reading stream output asynchronously is a bit broken - not all the data is read before the process exits. Even if you call Process.WaitForExit()
and even if you then call Process.Close()
(or Dispose()
) you can still get a lot of data afterwards. See http://alabaxblog.info/2013/06/redirectstandardoutput-beginoutputreadline-pattern-broken/ for a full write-up, but the solution is basically to use synchronous methods. To avoid a deadlock, though, you have to call one of them on another thread:
using (var process = Process.Start(processInfo))
{
// Read stderr synchronously (on another thread)
string errorText = null;
var stderrThread = new Thread(() => { errorText = process.StandardError.ReadToEnd(); });
stderrThread.Start();
// Read stdout synchronously (on this thread)
while (true)
{
var line = process.StandardOutput.ReadLine();
if (line == null)
break;
// ... Do something with the line here ...
}
process.WaitForExit();
stderrThread.Join();
// ... Here you can do something with errorText ...
}
There are few things that are getting in the way of it...
The console app is probably using "\b" backspace to overwrite the percentage, its maybe not flushing to the stdout
stream after every write, and the BeginOutputReadLine
presumably waits for the end of line before giving you data.
See how you get on with reading process.StandardOutput.BaseStream
via BeginRead
(this code isn't proper async and the "\b"s will need processed differently if your putting progress in a form):
while (true)
{
byte[] buffer = new byte[256];
var ar = myProcess.StandardOutput.BaseStream.BeginRead(buffer, 0, 256, null, null);
ar.AsyncWaitHandle.WaitOne();
var bytesRead = myProcess.StandardOutput.BaseStream.EndRead(ar);
if (bytesRead > 0)
{
Console.Write(Encoding.ASCII.GetString(buffer, 0, bytesRead));
}
else
{
myProcess.WaitForExit();
break;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With