C# Process.StandardInput.Write deadlocks/hangs when not using StreamWriter.Close

Viewed 80

I have a C# program that wants to interact with an external process written in C++. I believe this C++ process is using correct standard input. I just can't seem to get my C# code to not hang when trying to write to Process.StandardInput.

I've seen countless examples using Process.StandardInput.Close() when done writing. Every StackOverflow answer I found says to use this, and it does work. The problem is I can't close the StreamWriter because I'm not done interacting with the process. The process is a state machine that holds variables created using stdin, parses expressions, and returns an evaluation. I am expected to keep giving the process input after each output.

Does anyone have an example where Process.StandardInput.WriteLine is used more than once without closing or restarting the process?

This is how the C++ process is reading input. This example simply echos back the input and waits for another.

int main () {
   std::string input;
   while (getline(std::cin, input)) {
      std::cout << input << std::endl;
   }
}

My C# program tries to interact with this process using this wrapper class.

public class Expression {

   System.Diagnostics.Process p;

   public Expression () {
      p = new System.Diagnostics.Process();
      p.StartInfo.UseShellExecute = false;
      p.StartInfo.RedirectStandardOutput = true;
      p.StartInfo.RedirectStandardInput = true;
      p.StartInfo.FileName = "InputEcho.exe";
      p.Start();
      p.StandardInput.AutoFlush = true;
   }

   public void Run (in string input, out string output) {
       p.StandardInput.WriteLine(input);
      // p.StandardInput.Close();
      output = p.StandardOutput.ReadToEnd();
   }
}

This works when I uncomment p.StandardInput.Close() but then subsequent calls to Expression.Run() won't work because the writer is closed.

Main program

Expression expn = new();
string output;

Console.WriteLine("Expression start");

expn.Run("Hello", output);
Console.WriteLine(output);

expn.Run("Hi", output);
Console.WriteLine(output);

Expected output

Expression start
Hello
Hi

Actual output

Expression start

EDIT:

@Matthew Andrews provided a really good answer that works, but it's not quite what I'm after. I didn't think about using event delegates to receive output data, and I see why: It's hard to implement this into the wrapper that I want to use to build a process-relevant API. What I mean by this is that I want to write some method that communicates with the process, give it input, receive the output, and return this data to the caller before doing anything else. My Expression.Run method exemplifies this perfectly.

Here's an example of what the root caller would look like in a greater C# program.

bool GetConditionEval (string condition, SomeDataType data) {

   // Makes another call to 'Run' that commands the C++ process to store a variable
   // Input looks like this: "variableName = true" (aka key/value pairs)
   Expression.SetVar(data.name, "true");

   // Don't ask why I'm using an external process to set variables using string expressions.
   // It's a company proprietary thing.

   string output;
   Expression.Run(in condition, out output);

   if (output.ToLower() == "true") return true;
   else if (output.ToLower() == "false") return false;
   else throw new Exception("Output is something other than true or false.");
}

This is why I'd like for Run to immediately return the output it receives from the process. If not, I guess I could find a way for a delegate method to store the output in a global container and the GetConditionEval can just reach into that. I worry about race conditions though.

Side note: Since I do expect the API that is contained in this C++ process to eventaully take other forms, spinning this up as a standalone process and invoking the API via stdin is really a stopgap for now so I don't have to convert thousands of lines of C++ code into C#.

SOLUTION:

I figured out a solution using the asynchronous method Matthew suggested while having a linear process of sending input and working immediately off the output in the same sequence. I reconfigured my wrapper class to queue each output received from the event listener. This sets up a pattern where I can call one method to send input, and then call another method right after to pop output data off the queue if any. I compensated for the fact that output data might not be avaliable immediately by simply waiting if the queue is empty and then moving forward once something is there. This unfortuately makes it a blocking call if it does have to wait, but it's the best I have so far. I also implemented a failsafe so it doesn't wait indefinately.

public class Expression {

   System.Diagnostics.Process p = new();
   System.Collections.Generic.Queue<string> outputQ = new();

   public Expression () {
      p.StartInfo.UseShellExecute = false;
      p.StartInfo.RedirectStandardOutput = true;
      p.StartInfo.RedirectStandardInput = true;
      p.StartInfo.FileName = "C2E2.exe";
      p.OutputDataReceived += (s, e) => {
         outputQ.Enqueue(e.Data);
      };
      p.Start();
      p.BeginOutputReadLine();
   }

   /// Returns custom exception object if error is encountered.
   public GRLib.Exception Run (in string input) {

      if (p == null) return GRLib.Exception.New("Expression Evaluator not operational.");

      try {
         p.StandardInput.WriteLine(input);
      }
      catch (Exception e) {
         return GRLib.Exception.New(e.Message);
      }

      return null;
   }

   /// Returns error code 1 if timeout occured.
   /// Timeout is represented in milliseconds.
   /// Blocking call.
   public GRLib.Exception GetOutput (out string output, int timeout = 2000) {

      /// Wait for something to show in the queue.
      /// Waits indefinitely if timeout is 0.

      /// If anyone knows a better way to implement this waiting loop,
      /// please let me know!

      int timeWaited = 0;
      while (outputQ.Count == 0) {
         System.Threading.Thread.Sleep(100);
         if (timeout != 0 && (timeWaited += 100) > timeout) {
            output = "ERR";
            return GRLib.Exception.New(1, "Get timed out.");
         }
      }

      output = outputQ.Dequeue();

      return null;
   }

...
}

Example usage

Expression expression = new();

var e = expression.Run("3 > 2");
if (e != null) // Handle error

string output;
e = expression.GetOutput(out output);
if (e != null) // Handle error

// 'output' should now be 'true' which can then be used in other parts of this program.

While the event listener in a standalone fashion works great, I need the output from the process to be returned in the same stack where the input is given because this is going to be part of a more complex call graph.

1 Answers

The problem you're observing is due to the synchronous nature of Process.StandardOutput.ReadToEnd(). Instead, you should listen for your output asynchronously by setting Process.BeginOutputReadLine() and utilizing the Process.OutputDataReceived event.

Here is a quick example to get you started:

var p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = @"ConsoleApplication1.exe";

p.OutputDataReceived += (s, e) =>
{
    Console.WriteLine(e.Data);
};

p.Start();
p.BeginOutputReadLine();

while (true)
{
    var readLine = Console.ReadLine();
    p.StandardInput.WriteLine(readLine);
}

And here is the c++ I used for ConsoleApplication1.exe:

int main()
{
    std::cout << "Hello World!\n";
    std::string input;
    while (std::getline(std::cin, input)) {
        std::cout << input << std::endl;
    }
}

Running my example will print Hello World! and then proceed to parrot whatever else you enter into the console.

Related