Get .NET Process object to flush input stream continously?
I am trying to change my class library that talks to the Mercurial command line client, and new in the 1.9 client is the ability to spin up a server and talk to it over the standard input/output pipes. This is really promising since one of the major headaches of using the Mercurial command line client is that since it is written in Python, there's a bit of overhead spinning up the client, even for just asking it for its version.
However, there is one problem. Up until now I have retrieved the output from the command line client by reading the standard output/error, and when the process exits, the streams flush and I can read the end of the stream.
However, in this new mode, the process will dump a lot of text to the standard error/output, and then sit around waiting for the next command. Great for reducing overhead, but lousy since .NET buffers those streams.
In other words, since the process does not exit, I do not get the last portion of that output until:
- I ask the process to exit
- I issue another command
Is there a way for me to
- Ask the buffer to flush, meaning that I get whatever is in the buffer, even if it is not enough to make the buffer flush by itself?
- If not, can I set the buffer-size to 1 character?
- Anything else I can do?
I can deal with P/Invoke if that is what it takes.
Here's a LINQPad proof-of-concept program that would illustrate:
What it does is spin up a command prompt and feed it the DIR command, however notice that not until those 10 seconds have elapsed inside the loop does the program output the "C:>" prompt that the command prompt outputted right after producing the directory listing.
In other words, this is what the command prompt does:
- Produce directory listing
- Ask for another command by prompting "C:>\"
However, this is what the program below sees:
- Produce directory listing
- (wait 10 seconds)
- Close the input stream
See the prompt
void Main() { string clientExecutablePath = "cmd.exe";
var psi = new ProcessStartInfo(); psi.FileName = clientExecutablePath; psi.RedirectStandardError = true; psi.RedirectStandardInput = true; psi.RedirectStandardOutput = true; psi.CreateNoWindow = true; psi.WorkingDirectory = @"C:\"; psi.WindowStyle = ProcessWindowStyle.Hidden; psi.UseShellExecute = false; psi.ErrorDialog = false; psi.StandardErrorEncoding = Encoding.GetEncoding("Windows-1252"); psi.StandardOutputEncoding = Encoding.GetEncoding("Windows-1252"); var p = Process.Start(psi); var input = p.StandardInput; var output = p.StandardOutput; var error = p.StandardError; Action<StreamReader, string> reader = delegate(Stre开发者_StackOverflow社区amReader streamReader, string prefix) { string line; while ((line = streamReader.ReadLine()) != null) { Debug.WriteLine(prefix + line); } }; IAsyncResult outputReader = reader.BeginInvoke(output, "o: ", null, null); IAsyncResult errorReader = reader.BeginInvoke(error, "e: ", null, null); input.Write("dir\n"); input.Flush(); while (!p.HasExited) { Thread.Sleep(10000); input.Close(); } reader.EndInvoke(outputReader); reader.EndInvoke(errorReader);
}
I don't think there's any way to force a flush, however I have a couple of alternatives which might help you:
- I notice you're using ReadLine() to read the stream. This won't return until a complete line of output (including CRLF, or at least LF) has been written. You might therefore want to consider using Read() instead, which will read only a single character. You may find this gets you the last chunk of output you're looking for if the server process isn't writing a CRLF on the last line until it exits.
- You could read the streams asynchronously by adding handlers to the OutputDataReceived and ErrorDataReceived events on the Process, then use BeginOutputReadLine and BeginErrorReadLine to start receiving data. I don't know how often the events are invoked though: at regular intervals if any data is available, whenever a complete line of data is received, or for every character.
HTH,
Bart
精彩评论