Johannes Johannes - 1 month ago 12
C# Question

Race conditition when using Process RedirectStandardError and RedirectStandardOutput,

I find myself in a race condition when subscribing to the output and error stream of

System.Diagnostics.Process
.
Here is a minimal example of what I do:

private string execute(string command, string arguments, int mstimeout)
{
string report = string.Empty;
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
Process p = new Process();
DataReceivedEventHandler ErrorDataReceived = (o, e) => { error.Append(e.Data); };
DataReceivedEventHandler OutputDataReceived = (o, e) => { output.Append(e.Data); };
try
{
p.StartInfo.FileName = command;
p.StartInfo.Arguments = arguments;
p.EnableRaisingEvents = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardOutput = true;
p.OutputDataReceived += OutputDataReceived;
p.ErrorDataReceived += ErrorDataReceived;
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
p.WaitForExit(mstimeout);
report = output.ToString() + "\n" + error.ToString();
}
finally
{
p.OutputDataReceived -= OutputDataReceived;
p.ErrorDataReceived -= ErrorDataReceived;
}
return report;
}


When debugging slowly the behaviour is what I hoped it would be. When running without stops the report ends up empty.

I assume there is a race condition where the underlying streaming objects are disposed before all the output was handled.

Is there something I can do to wait for all the output to be processed?

Answer

The problem was a timeout in certain situations. I needet to Kill the Process to avoid follow up issues.

            if(!p.WaitForExit(mstimeout))
            {
                p.Kill();
            }

For good measure I threw in a cleanup in the finally part that is likely not needet.

        finally
        {
            p.OutputDataReceived -= OutputDataReceived;
            p.ErrorDataReceived -= ErrorDataReceived;
            p.Dispose();
            p = null;
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
        }

Edit: I deleted the finally part since the comment below seems correct.

Edit: There was a deeper problem where the timeout was hit because of a required input. I ended up invoking a different command that is silent.