tags:

views:

201

answers:

3

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.

A: 

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
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
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
A: 
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
Same comment as before - I need to be calling EndOfStream all the time to check if new data arrived. Looking for a smarter way here..
Michael Pliskin
ReadToEnd() will block the execution until stream is closed... which is not what I need.
Michael Pliskin
+1  A: 

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).

BartS
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