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


facebooktwittergoogle_pluslinkedinmail

Reading redirected child output seems to be an easy task. But there are developers that struggle with child process hanging because of console write / read deadlocks. And deadlocks are never easy to investigate.

Let’s consider a common example – a parent process starts a child process and reads all its output. Here is an example of application that can simulate the scenario.

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()
        {
            Console.WriteLine("Sample output");
            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))
            {
                process.WaitForExit();
                var output = process.StandardOutput.ReadToEnd();

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

            return 0;
        }
    }
}

The application can run in two modes:

  • Parent process – executes child process and reads its output
  • Child process – prints some output

It all works fine, but let’s consider a scenario where the child output is meaningful bigger.

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

            return 0;
        }

This time the parent process will hang – it’s a deadlock. Why? Let’s think what the parent does – first it setup child process output redirection.

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

Next it starts the child process.

            using (var process = Process.Start(processInfo))

Consecutive step is waiting for child process to finish.

            process.WaitForExit();

And that’s the point where the parent process deadlocks with the child. Why? The parent is waiting for the child to finish and the child has some dependency on the parent as well.

First, you need to understand how output redirection works. There is a buffer created for the output. When the child is writing to the console it’s actually writing to the buffer. If the child writes a lot the buffer might get full. In such case the child hangs on Console.Write  until the buffer gets some space.

The parent should read the child output before it waits for child to finish. So to fix the deadlock we have to swap following lines.

            process.WaitForExit();
            var output = process.StandardOutput.ReadToEnd(); // <-- This line should be the first

The full parent code should look like below.

        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;
        }

You can find more details in the ProcessStartInfo.RedirectStandardOutput documentation.

Summary

Please note that exactly the same issue might happen with standard error output (stderr). Additionally similar deadlock might happen with writing to child’s standard input.

Basically you need to be very careful and understand what you’re doing if you want to read child’s input / output in a synchronous way.

 

facebooktwittergoogle_pluslinkedinmail

Leave a comment

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

2 thoughts on “How to avoid deadlocks when reading redirected child console in C#

  • Ganesh Sittampalam

    Won’t this still deadlock if the process produces too much on stderr? I think to be really safe you have to use the events instead.

    • Mariusz Bojkowski Post author

      Correct, I’ve added a note about stderr.

      I agree that asynchronous handling is a safer way. I’ll write a post about it as well.