3

Does C# provide an event when data is received on the stdin stream for my own process? Something like Process.OutputDataReceived, only I need an event for InputDataReceived.

I've searched high and low, and learned to redirect stdin->stdout, monitor output streams of spawned apps and a ton of other stuff, but nowhere has anyone shown which event is triggered when stdin is recieved. Unless I use a dumb polling loop in main().

// dumb polling loop -- is this the only way? does this consume a lot of CPU?
while ((line = Console.ReadLine()) != null && line != "") {
     // do work
}

Also, I need to get binary data from the stream, something like this:

using (Stream stdin = Console.OpenStandardInput())
using (Stream stdout = Console.OpenStandardOutput())
{
    byte[] buffer = new byte[2048];
    int bytes;
    while ((bytes = stdin.Read(buffer, 0, buffer.Length)) > 0) {
        stdout.Write(buffer, 0, bytes);
    }
}
Robin Rodricks
  • 110,798
  • 141
  • 398
  • 607

2 Answers2

4

The polling loop won't consume much CPU, because ReadLine blocks and waits. Put this code in an own worker-thread and raise your event out of it. As far as I know, there is no such feature in .NET.

EDIT: I was wrong here in the first place. Corrected:

You can actually read the binary data from stdin, as this SO answer says:

To read binary, the best approach is to use the raw input stream - here showing something like "echo" between stdin and stdout:

using (Stream stdin = Console.OpenStandardInput())
using (Stream stdout = Console.OpenStandardOutput())
{
    byte[] buffer = new byte[2048];
    int bytes;
    while ((bytes = stdin.Read(buffer, 0, buffer.Length)) > 0) {
        stdout.Write(buffer, 0, bytes);
    }
}
Community
  • 1
  • 1
Patrik
  • 1,355
  • 12
  • 22
  • Why cannot I use `stdin.Read(..)` to read binary data directly? Why is base64 needed? Are the `stdin` streams limited to ASCII characters? – Robin Rodricks Jul 20 '15 at 09:13
  • Sorry, I was wrong on the stdin.read-binary topic. I updated my answer. – Patrik Jul 20 '15 at 09:16
  • 1
    No, they aren't. Base64 isn't needed. You can use the asynchronous methods like [ReadAsync](https://msdn.microsoft.com/en-us/library/hh137813(v=vs.110).aspx) to avoid blocking entirely and achieve exactly what you want. – Panagiotis Kanavos Jul 20 '15 at 09:16
  • @PanagiotisKanavos - Can you add an answer for async reading of streams? – Robin Rodricks Jul 20 '15 at 09:26
  • 1
    @Geotarget first, why do you want to do this? Unless you have another thread processing the input, asynchronously reading the data won't have any benefit – Panagiotis Kanavos Jul 20 '15 at 09:30
2

Here's an async approach. Like OutputDataReceived, the callback runs on newlines. For binary, streaming to base64 might work. Switching it to a binary stream is harder because you can't just check for newline.

using System.Diagnostics;
using System.Threading.Tasks;

public static void ListenToParent(Action<string> onMessageFromParent)
{
    Task.Run(async () =>
    {
        while (true) // Loop runs only once per line received
        {
            var text = await Console.In.ReadLineAsync();
            onMessageFromParent(text);
        }
    });
}

Here's how my parent app sets up the child process:

var child = new Process()
{
    EnableRaisingEvents = true,
    StartInfo =
    {
        FileName = ..., // .exe path
        RedirectStandardOutput = true,
        RedirectStandardInput = true,
        UseShellExecute = false,
        CreateNoWindow = true
    },
};

child.Start();
child.BeginOutputReadLine();

... and how it sends a line to the child process:

child.StandardInput.WriteLine("Message from parent");
Vimes
  • 10,577
  • 17
  • 66
  • 86