21 July 2009

 

The correct way to redirect standard streams in .NET

If you're wanting to redirect the standard IO streams for a command line process from C#, most google results just create a new Process() then call StandardOutput.ReadToEnd(). Unfortunately this is a simplistic and naive approach that fails to account for the buffered nature of the streams.

You see, if the process outputs significantly to both stdout and stderr, using the simplistic approach above wil lcause the stream buffer to fill with unread stderr content before all the stdout content is read. When this happens, ReadToEnd stalls, waiting for more stdout, but none will be arrive because the buffer is full of stderr, so no new stdout can be written to it.

The correct approach is to read from both stdout and stderr asynchronously, via a delegates and BeginInvoke/EndInvoke. While we're at it, it's pretty easy to provide input to stdin, completing the trilogy. What's more, it all wraps up into a nice little self-contained method:

// Interface for reading stdout and stderr of the child process.
private delegate string AsyncStringReader();

private void Run( string cmd, string args, string stdin, out string stdout, out string stderr )
{
// Redirect std streams of the child process.
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;

// Run the command, feeding the provided input.
p.StartInfo.FileName = cmd;
p.StartInfo.Arguments = args;
p.Start();
p.StandardInput.Write( stdin );
p.StandardInput.Close();

// Stdout and stderr need to be read asynchronously, otherwise their
// internal buffers fill up (<6k characters) and block, preventing
// the child process from finishing its execution.
AsyncStringReader outReader = new AsyncStringReader( p.StandardOutput.ReadToEnd );
AsyncStringReader errReader = new AsyncStringReader( p.StandardError.ReadToEnd );
IAsyncResult outTask = outReader.BeginInvoke( null, null );
IAsyncResult errTask = errReader.BeginInvoke( null, null );
p.WaitForExit(); // possibly not needed

// Return the output streams.
stdout = outReader.EndInvoke( outTask );
stderr = errReader.EndInvoke( errTask );
}

Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?