How to avoid deadlocks when reading redirected child console in C# (part 2)


Facebooktwittergoogle_pluslinkedinmail

Some time ago, I showed a way to avoid deadlocks when reading the redirected console of a child process in a synchronous way. It works, but only when we have to deal with a single output stream – standard output or standard error stream. But what should be done when both streams are flooded with data?

The final application is available on GitHub.

Test case

Let’s start with the code that works just with a single stream.

using System;
using System.Diagnostics;

namespace ChildConsoleDeadlock
{
    public class Program
    {
        private static string ChildParam = "-child";

        public static int Main(string[] args)
        {
            if (args.Length == 1 && args[0] == ChildParam)
            {
                return BeChild();
            }

            return BeParent();
        }

        private static int BeChild()
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine("This is text that will be written 1000 times.");
            }

            return 0;
        }

        private static int BeParent()
        {
            var processInfo = new ProcessStartInfo
            {
                FileName = Environment.CommandLine.Replace(".vshost", string.Empty),
                Arguments = ChildParam,
                UseShellExecute = false,
                RedirectStandardOutput = true
            };

            using (var process = Process.Start(processInfo))
            {
                var output = process.StandardOutput.ReadToEnd();
                process.WaitForExit();

                Console.WriteLine("Child output: " + output);
            }

            return 0;
        }
    }
}

Now, let’s extend the child process behavior and print some data to the error stream.

        private static int BeChild()
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine("This is text that will be written 1000 times.");
            }

            Console.Error.WriteLine("Some error text");

            return 0;
        }

We also have to redirect the error stream and read it’s content – updates to the parent process.

        private static int BeParent()
        {
            var processInfo = new ProcessStartInfo
            {
                FileName = Environment.CommandLine.Replace(".vshost", string.Empty),
                Arguments = ChildParam,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            };

            using (var process = Process.Start(processInfo))
            {
                var output = process.StandardOutput.ReadToEnd();
                var error = process.StandardError.ReadToEnd();
                process.WaitForExit();

                Console.WriteLine("OUTPUT stream: " + output);
                Console.WriteLine("ERROR stream: " + error);
            }

            return 0;
        }

The application runs with no issue.

OUTPUT stream: This is text that will be written 1000 times.
This is text that will be written 1000 times.
This is text that will be written 1000 times.
(...)
This is text that will be written 1000 times.

ERROR stream: Some error text

Press any key to continue . . .

We have to put more data to the error stream.

        private static int BeChild()
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine("This is text that will be written 1000 times.");
                Console.Error.WriteLine("Error text written 1000 times.");
            }

            return 0;
        }

This time application deadlocks. This is because we try to read streams in sequence – first the output stream, then the error stream. The applications blocks on reading output stream while the error stream buffer gets filled up. We could try changing the order.

                var error = process.StandardError.ReadToEnd();
                var output = process.StandardOutput.ReadToEnd();
                process.WaitForExit();

This doesn’t help – this time the application blocks on reading error stream while the output stream buffer gets filled up.

Event-based output reading

To solve the problem we have to switch to event-based reading.

            using (var process = Process.Start(processInfo))
            {
                process.OutputDataReceived += (sender, eventArgs) => Console.WriteLine("OUTPUT: " + eventArgs.Data);
                process.ErrorDataReceived += (sender, eventArgs) => Console.WriteLine("ERROR: " + eventArgs.Data);

                process.BeginOutputReadLine();
                process.BeginErrorReadLine();

                process.WaitForExit();
            }

First, we add event handlers to OutputDataReceived and  ErrorDataReceived events. Next, we start reading both streams and wait for the process exit. The event handlers are called in random order but they are consuming data. This way both streams’ buffers will be cleared and it won’t cause the application deadlock.

But what if someone needs all the output/error data in one string? It’s also possible, we just need to combine the data ourselves.

            using (var process = Process.Start(processInfo))
            {
                var output = new StringBuilder();
                var error = new StringBuilder();

                process.OutputDataReceived += (sender, eventArgs) => output.AppendLine(eventArgs.Data);
                process.ErrorDataReceived += (sender, eventArgs) => error.AppendLine(eventArgs.Data);

                process.BeginOutputReadLine();
                process.BeginErrorReadLine();

                process.WaitForExit();

                Console.WriteLine("OUTPUT stream: " + output);
                Console.WriteLine("ERROR stream: " + error);
            }

Handler method adds the data to StringBuilder, so it can be consumed in one piece later.

OUTPUT stream: This is text that will be written 1000 times.
This is text that will be written 1000 times.
This is text that will be written 1000 times.
(...)
This is text that will be written 1000 times.


ERROR stream: Error text written 1000 times.
Error text written 1000 times.
Error text written 1000 times.
(...)
Error text written 1000 times.


Press any key to continue . . .

The full example is available on GitHub.

 

 

Facebooktwittergoogle_pluslinkedinmail

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.