Consider the following program. Here I start a simple process and want to deal with it's output. I assumed that would be the case after WaitForExit has returned, but it turns out, that I have to wait upto a full second until that output actually arrives in my program.
static void Main(string[] args)
{
using var p = new Process();
p.StartInfo.FileName = "echo";
p.StartInfo.Arguments = "I apologize for being late";
p.StartInfo.CreateNoWindow = false;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
var stdError = new StringBuilder();
var stdOutput = new StringBuilder();
p.ErrorDataReceived += (sender, args) => stdError.AppendLine(args.Data);
p.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data);
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
// without the int-parameter here, it works
while (!p.WaitForExit(10000))
Console.WriteLine("still waiting...");
string a = stdOutput.ToString();
string b = stdError.ToString();
Thread.Sleep(1000);
string c = stdOutput.ToString();
string d = stdError.ToString();
Console.WriteLine("output before sleep: " + a);
Console.WriteLine("error before sleep: " + b);
Console.WriteLine("output after sleep: " + c);
Console.WriteLine("error after sleep: " + d);
}
output
output before sleep:
error before sleep:
output after sleep: I apologize for being late
error after sleep:
Here I would expect, that a and c have the exact same value. But that is not the case. How would I modify this example such that I reliable receive the full output of the process, but without calling Thread.Sleep(1000)?
Notes:
p.WaitForExit() instead p.WaitForExit(10000) everything seems to workp.StandardOutput.ReadToEnd() for both streams it seems to work. But I am told by the official documentation, that this would lead to deadlocksp.StandardError.ReadToEnd() while using the async solution for the output, than the output still arrives late.p.WaitForExit()without any parameter already doesn't work. Also they are not interested in the output at all.There is an awkward implementation detail that is at play here.
Calling
p.WaitForExit();
and
p.WaitForExit(10000);
do slightly different things when the actual native processhandle gets signaled.
Internally p.WaitForExit(); calls p.WaitForExit(-1);. The -1 is significant here. Let's see what we have (code is simplified/ paraphrased to show the essence):
public bool WaitForExit(int milliseconds)
{
// init stuff removed
bool flag;
try
{
flag = processWaitHandle.WaitOne(milliseconds, false);
}
finally
{
// here we see our -1 return
if (this.output != null && milliseconds == -1)
{
this.output.WaitUtilEOF();
}
}
return flag;
}
In the above snippet you see this.output.WaitUtilEOF(); and that calls into an internal AsyncStreamReader that employs a queue. The call to WaitUtilEOF(); basically waits on the stream for the EOF event to be raised.
There is no other way that I could find to force the Process class to make a call to wait for those EOF events. The only option is to call WaitForExit() without a parameter. There is however no penalty in calling WaitForExit(); after a call to WaitForExit(10000) returned.
So if your timeout on the first WaitForExit(10000) was reached but you're sure you rather wait a bit longer for the AsyncStreamReader to hand you all data it has, call WaitForExit() without a parameter to have both AsyncStreamReaders empty their queue and then return control to you. This does mean that if your process didn't end you're now stuck in a wait that won't ever resolve itself unless you kill the child process by yourself.