lonious
lonious

Reputation: 696

How to properly read c# process standard error stream without deadlocks?

It really feels like I'm following best practices according to MSDN. But I'm most likely missing something because my code hangs after continuing from this line: string errorOutput = cmd.StandardError.ReadToEnd();. What am I doing wrong?

        var batchfile = File.OpenWrite("run.bat");
        StreamWriter writer = new StreamWriter(batchfile);
        writer.Write("dotnet run" + '\n');
        //writer.Write("set /p temp=\"Hit enter to continue\"" + '\n');
        writer.Close();

        var cmd = new Process();
        cmd.StartInfo.FileName = batchfile.Name;
        cmd.StartInfo.UseShellExecute = false;
        cmd.StartInfo.RedirectStandardError = true;

        cmd.Start();
        string errorOutput = cmd.StandardError.ReadToEnd();
        cmd.WaitForExit();

        var outputfile = File.OpenWrite("run_errorOut.txt");
        StreamWriter outputWriter = new StreamWriter(outputfile);
        outputWriter.Write(errorOutput);
        outputWriter.Close();

In case it helps: At the moment this code is running inside an xunit test inside a dotnetcore2.2 app (its target framework is 'netcoreapp2.2').

Upvotes: 4

Views: 1897

Answers (1)

NO TO OPENAI
NO TO OPENAI

Reputation: 685

So I had a very similar problem not that long ago, doing almost exactly the same and what I found was that:

  • Trying to read StandardError/Output without there being anything to read blocks until there is something to read (or deadlock if nothing is ever written - e.g. process has exited)
  • cmd.HasExited is false until the StandardError/Output has been read so that cannot be used to detect when finished (and then read StandardError/Output)

The most reliable way I have found to do this is to use the provided events on Process:

var outData = new StringBuilder();
var errData = new StringBuilder();    

cmd.OutputDataReceived += (sender, args) => outData.Append(args.Data ?? string.Empty);
cmd.ErrorDataReceived += (sender, args) => errData.Append(args.Data ?? string.Empty);

And then after cmd.Start() == true:

cmd.BeginErrorReadLine();
cmd.BeginOutputReadLine();

To start the events firing.

After the process has exited you can call .ToString() on the StringBuilders to get the data:

Console.WriteLine(outData.ToString());
Console.WriteLine(errData.ToString());

(Note that .ToString() can be implicit when using Console.WriteLine)

Upvotes: 8

Related Questions