using System; using System.Diagnostics; using System.IO; using System.Text; using System.Threading;  /*************************************************************************************** * Author: Curt C. * Email : harpyeaglecp@aol.com * * Project File: "Solving Problems of Monitoring Standard Output and Error Streams Of A Running Process" * for http://www.codeproject.com * * This software is released under the Code Project Open License (CPOL) * * See official license description at: http://www.codeproject.com/info/cpol10.aspx * * This software is provided AS-IS without any warranty of any kind. * * >>> Please leave this header intact when using this file in other projects <<< ***************************************************************************************/ namespace ProgramQueuer.Queuer { // Delegate definition used for events to receive notification // of text read from stdout or stderr of a running process. public delegate void StringReadEventHandler(string text); /// /// Class that manages the reading of the output produced by a given 'Process' /// and reports the output via events. /// Both standard error (stderr) and standard output (stdout) are /// managed and reported. /// The stdout and stderr monitoring and reading are each performed /// by separate background threads. Each thread blocks on a Read() /// method, waiting for text in the stream being monitored to become available. /// /// Note the Process.RedirectStandardOutput must be set to true in order /// to read standard output from the process, and the Process.RedirectStandardError /// must be set to true to read standard error. /// public class ProcessIOManager { #region Private_Fields // Command line process that is executing and being // monitored by this class for stdout/stderr output. private Process runningProcess; // Thread to monitor and read standard output (stdout) private Thread stdoutThread; // Thread to monitor and read standard error (stderr) private Thread stderrThread; // Buffer to hold characters read from either stdout, or stderr streams private StringBuilder streambuffer; #endregion #region Public_Properties_And_Events /// /// Gets the process being monitored /// /// The running process. public Process RunningProcess { get { return runningProcess; } set { runningProcess = value; } } // Event to notify of a string read from stdout stream public event StringReadEventHandler StdoutTextRead; // Event to notify of a string read from stderr stream public event StringReadEventHandler StderrTextRead; #endregion #region Constructor_And_Initialization /// /// Initializes a new instance of the class. /// /// public ProcessIOManager() { this.streambuffer = new StringBuilder(256); } /// /// Initializes a new instance of the class. /// /// The process. /// /// Does not automatically start listening for stdout/stderr. /// Call StartProcessOutputRead() to begin listening for process output. /// /// public ProcessIOManager(Process process) { if (process == null) throw new Exception("ProcessIoManager unable to set running process - null value is supplied"); if (process.HasExited == true) throw new Exception("ProcessIoManager unable to set running process - the process has already existed."); this.runningProcess = process; this.streambuffer = new StringBuilder(256); } #endregion #region Public_Methods /// /// Starts the background threads reading any output produced (standard output, standard error) /// that is produces by the running process. /// public void StartProcessOutputRead() { // Just to make sure there aren't previous threads running. StopMonitoringProcessOutput(); // Make sure we have a valid, running process CheckForValidProcess("Unable to start monitoring process output.", true); // If the stdout is redirected for the process, then start // the stdout thread, which will manage the reading of stdout from the // running process, and report text read via supplied events. if (runningProcess.StartInfo.RedirectStandardOutput == true) { stdoutThread = new Thread(new ThreadStart(ReadStandardOutputThreadMethod)); // Make thread a background thread - if it was foreground, then // the thread could hang up the process from exiting. Background // threads will be forced to stop on main thread termination. stdoutThread.IsBackground = true; stdoutThread.Start(); } // If the stderr is redirected for the process, then start // the stderr thread, which will manage the reading of stderr from the // running process, and report text read via supplied events. if (runningProcess.StartInfo.RedirectStandardError == true) { stderrThread = new Thread(new ThreadStart(ReadStandardErrorThreadMethod)); stderrThread.IsBackground = true; stderrThread.Start(); } } /// /// Writes the supplied text string to the standard input (stdin) of the running process /// /// In order to be able to write to the Process, the StartInfo.RedirectStandardInput must be set to true. /// The text to write to running process input stream. public void WriteStdin(string text) { // Make sure we have a valid, running process CheckForValidProcess("Unable to write to process standard input.", true); if (runningProcess.StartInfo.RedirectStandardInput == true) runningProcess.StandardInput.WriteLine(text); } #endregion #region Private_Methods /// /// Checks for valid (non-null Process), and optionally check to see if the process has exited. /// Throws Exception if process is null, or if process has existed and checkForHasExited is true. /// /// The error message text to display if an exception is thrown. /// if set to true [check if process has exited]. private void CheckForValidProcess(string errorMessageText, bool checkForHasExited) { errorMessageText = (errorMessageText == null ? "" : errorMessageText.Trim()); if (runningProcess == null) throw new Exception(errorMessageText + " (Running process must be available)"); if (checkForHasExited && runningProcess.HasExited) throw new Exception(errorMessageText + " (Process has exited)"); } /// /// Read characters from the supplied stream, and accumulate them in the /// 'streambuffer' variable until there are no more characters to read. /// /// The first character that has already been read. /// The stream reader to read text from. /// if set to true the stream is assumed to be standard output, otherwise assumed to be standard error. private void ReadStream(int firstCharRead, StreamReader streamReader, bool isstdout) { // One of the streams (stdout, stderr) has characters ready to be written // Flush the supplied stream until no more characters remain to be read. // The synchronized/ locked section of code to prevent the other thread from // reading its stream at the same time, producing intermixed stderr/stdout results. // If the threads were not synchronized, the threads // could read from both stream simultaneously, and jumble up the text with // stderr and stdout text intermixed. lock (this) { // Single character read from either stdout or stderr int ch; // Clear the stream buffer to hold the text to be read streambuffer.Length = 0; //Console.WriteLine("CHAR=" + firstCharRead); streambuffer.Append((char)firstCharRead); // While there are more characters to be read while (streamReader.Peek() > -1) { // Read the character in the queue ch = streamReader.Read(); if (ch != '\n' && streambuffer.Length > 0 && streambuffer[streambuffer.Length - 1] == '\r') NotifyAndFlushBufferText(streambuffer, isstdout); // Accumulate the characters read in the stream buffer streambuffer.Append((char)ch); // Send text one line at a time - much more efficient than // one character at a time if (ch == '\n') NotifyAndFlushBufferText(streambuffer, isstdout); } // Flush any remaining text in the buffer NotifyAndFlushBufferText(streambuffer, isstdout); } // End lock() } /// /// Invokes the OnStdoutTextRead (if isstdout==true)/ OnStderrTextRead events /// with the supplied streambuilder 'textbuffer', then clears /// textbuffer after event is invoked. /// /// The textbuffer containing the text string to pass to events. /// if set to true, the stdout event is invoked, otherwise stedrr event is invoked. private void NotifyAndFlushBufferText(StringBuilder textbuffer, bool isstdout) { if (textbuffer.Length > 0) { try { if (isstdout == true && StdoutTextRead != null) { // Send notificatin of text read from stdout StdoutTextRead(textbuffer.ToString()); } else if (isstdout == false && StderrTextRead != null) { // Send notificatin of text read from stderr StderrTextRead(textbuffer.ToString()); } } catch (Exception e) { } // 'Clear' the text buffer textbuffer.Length = 0; } } /// /// Method started in a background thread (stdoutThread) to manage the reading and reporting of /// standard output text produced by the running process. /// private void ReadStandardOutputThreadMethod() { // Main entry point for thread - make sure the main entry method // is surrounded with try catch, so an uncaught exception won't // screw up the entire application try { // character read from stdout int ch; // The Read() method will block until something is available while (runningProcess != null && (ch = runningProcess.StandardOutput.Read()) > -1) { // a character has become available for reading // block the other thread and process this stream's input. ReadStream(ch, runningProcess.StandardOutput, true); } } catch (Exception ex) { Console.WriteLine("ProcessIoManager.ReadStandardOutputThreadMethod()- Exception caught:" + ex.Message + "\nStack Trace:" + ex.StackTrace); } } /// /// Method started in a background thread (stderrThread) to manage the reading and reporting of /// standard error text produced by the running process. /// private void ReadStandardErrorThreadMethod() { try { // Character read from stderr int ch; // The Read() method will block until something is available while (runningProcess != null && (ch = runningProcess.StandardError.Read()) > -1) ReadStream(ch, runningProcess.StandardError, false); } catch (Exception ex) { Console.WriteLine("ProcessIoManager.ReadStandardErrorThreadMethod()- Exception caught:" + ex.Message + "\nStack Trace:" + ex.StackTrace); } } /// /// Stops both the standard input and stardard error background reader threads (via the Abort() method) /// public void StopMonitoringProcessOutput() { // Stop the stdout reader thread try { if (stdoutThread != null) stdoutThread.Abort(); } catch (Exception ex) { Console.WriteLine("ProcessIoManager.StopReadThreads()-Exception caught on stopping stdout thread.\n" + "Exception Message:\n" + ex.Message + "\nStack Trace:\n" + ex.StackTrace); } // Stop the stderr reader thread try { if (stderrThread != null) stderrThread.Abort(); } catch (Exception ex) { Console.WriteLine("ProcessIoManager.StopReadThreads()-Exception caught on stopping stderr thread.\n" + "Exception Message:\n" + ex.Message + "\nStack Trace:\n" + ex.StackTrace); } stdoutThread = null; stderrThread = null; } #endregion } }