1

I am launching another process from by C# application, and came to a problem: is there any way to read process output char-by-char, not line-by-line? I desperately need that because the other process is a command-line engine, and I need to send a command to it, read output and somehow understand when it is done. The way it indicates that is to write a prompt WITHOUT a newline, so I have no chance to ever capture that. Using fragile timer stuff now, and looking for a better solution.

C# Process API as outlined in MSDN and other places only allows for line-by-line input - even in async way.

Community
  • 1
  • 1
Michael Pliskin
  • 2,352
  • 4
  • 26
  • 42

3 Answers3

1

This is a sample program that starts a process, and reads standard output, character by character and not line by line:

    static void Main(string[] args)
    {
        var process = new Process();
        process.StartInfo = new ProcessStartInfo(@"C:\Windows\System32\cmd.exe", "/c dir");
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.Start();
        var outReader = process.StandardOutput;
        while (true)
        {
            if (!outReader.EndOfStream)
                Console.Write((char)outReader.Read() + ".");
            if (outReader.EndOfStream)
                Thread.Sleep(1);
        }
    }

A small disclaimer--this was knocked up in no time, and I haven't proved out the issues around timing (what if the process exits before we've grabbed standard output -- very unlikely, I know). As far as your particular issue is concerned however, it shows that one doesn't need to read line by line (you're dealing with a StreamReader after all).

UPDATE: As per suggestions, included a Thread.Sleep(1) to yield the thread. Apparently there are issues with using Thread.Sleep(0), despite MSDN documentation on the matter (lower priority threads aren't given time). And yes, this is an infinite loop, and there'd have to be some thread management stuff involved to finish-off once the process has completed.

Eric Smith
  • 5,262
  • 2
  • 33
  • 49
  • This requires a separate thread to be checking EndOfStream property all the time which is a way to high CPU usage for nothing. I am looking for a way for stream to notify me when a new byte arrives - any ideas? – Michael Pliskin Aug 14 '09 at 09:37
  • I would probably do something similar. Maybe add in a tiny sleep and put it in a class with a NewChar event or something. – Svish Aug 14 '09 at 10:10
  • Is there any reason you couldn't just use the StreamReader's BaseStream property and use the BeginRead and EndRead methods to do it in an async manor? – Caleb Vear Nov 08 '13 at 01:31
1

I haven't tried it, but the following approach might work to avoid busy looping:

  1. Redirect the command line output to a file.
  2. Use the FindFirstChangeNotification from the Win32 API (with flag FILE_NOTIFY_CHANGE_SIZE) to detect file size changes.

That's an awful lot of work just to prevent busy looping however. Personally, I'd suggest an incremental backoff wait loop, to prevent using too much system resources. Something like (pseudocode):

int waittime = 1;
bool done = false;
while (true)
{
  ReadAsMuchAsIsAvailable();
  if (TheAvailableOutputEndsInAPrompt())
  {
    break;
  }
  Sleep (waittime);
  waittime++;                     // increase wait time
  waittime = min(waittime, 1000); // max 1 second
}

This will yield quick end-of-operation detection for quick commands, and slower for slow commands. That means that you can do a large series of quick commands and have their ends be detected quickly, while longer commands may have an overhead of up to 1 second (which won't be noticeable given the size of the commands themselves).

  • Thanks, this is what I've finally done (incremental backoff wait loop). Launched this loop as a separate thread and it works nicely now. – Michael Pliskin Aug 17 '09 at 20:06
0
char[] x = {' '};

System.IO.StreamReader reader = new StreamReader("c:\\file.txt");   



        while (!reader.EndOfStream)
        {
            reader.ReadBlock(x, 0, 1);      
            // x contains the current char in the reader.              
        }

This should work.

Edit:

Then how about using this:

 x = reader.ReadToEnd().ToCharArray();
TestSubject09
  • 411
  • 3
  • 8
  • 15