Nothing and Everything

2010.10.05

Multiple threads for C# Process streams

Filed under: Programming — kevenker @ 7:35 pm

Here’s my stab at writing a function that uses multiple threads for reading from a C# Process object’s streams, StandardError and StandardOutput. I’ve provided a wrapper function RunShellCommand on top of RunProcess which is the most generic. If you don’t want to run as a different user, pass in a null or zero-length string for username.

Note that workingDirectory MUST be set to something valid if you run as a different user. Set to null if you want to run in current directory.

The reason for the lock() in GetOutput is that I want to create a function that outputs to one StringBuilder, not two. If you will always have two StringBuilders, then you can get rid of the lock.

I’m super-new to threads, so be kind for any obvious mistakes! :)

 

        public void RunShellCommand(

            string username

            , string domain

            , SecureString password

            , string workingDirectory

            , string command

            , out string stdOut

            , out string stdErr

            , out int exitCode

            , int timeoutSeconds)

        {

            string cmd = “/c” + command;

            string fileName = System.Environment.ExpandEnvironmentVariables(“%comspec%”);

 

            RunProcess(

                username

                , domain

                , password

                , workingDirectory

                , fileName

                , cmd

                , out stdOut

                , out stdErr

                , out exitCode

                , timeoutSeconds);

        }

 

        public void RunProcess(

            string username

            , string domain

            , SecureString password

            , string workingDirectory

            , string fileName

            , string arguments

            , out string stdOut

            , out string stdErr

            , out int exitCode

            , int timeoutSeconds)

        {

            Process p = new Process();

            p.StartInfo.FileName = fileName;

            p.StartInfo.Arguments = arguments;

            p.StartInfo.UseShellExecute = false;

            p.StartInfo.RedirectStandardOutput = true;

            p.StartInfo.RedirectStandardError = true;

            p.StartInfo.CreateNoWindow = true;

            p.StartInfo.WorkingDirectory = workingDirectory;

 

            if (!string.IsNullOrEmpty(username))

            {

                p.StartInfo.UserName = username;

                p.StartInfo.Domain = domain;

                p.StartInfo.Password = password;

            }

 

            StringBuilder sbOut = new StringBuilder();

            StringBuilder sbErr = new StringBuilder();

 

            List<object> lOut = new List<object>();

            List<object> lErr = new List<object>();

 

            p.Start();

 

            lOut.Add(p.StandardOutput);

            lOut.Add(sbOut);

 

            lErr.Add(p.StandardError);

            lErr.Add(sbErr);

 

            System.Threading.Thread outThread = new System.Threading.Thread(GetOutput);

            System.Threading.Thread errThread = new System.Threading.Thread(GetOutput);

 

            outThread.IsBackground = true;

            errThread.IsBackground = true;

 

            outThread.Start(lOut);

            errThread.Start(lErr);

 

            if (!p.WaitForExit(timeoutSeconds * 1000))

            {

                throw new ApplicationException(

                    string.Format(

                    “Executing {0} {1} timed out!”

                    , p.StartInfo.FileName

                    , p.StartInfo.Arguments));

            }

 

            // 

            // Wait for readers grab all data before closing streams.

            // Closing the stream before it has finished may cause data-loss and may 

            // cause an ObjectDisposedException on the thread but this is trapped. 

            //

            errThread.Join(timeoutSeconds * 1000);

            outThread.Join(timeoutSeconds * 1000);

 

            p.StandardError.Close();

            p.StandardOutput.Close();

 

            stdOut = sbOut.ToString();

            stdErr = sbErr.ToString();

            exitCode = p.ExitCode;

        }

 

        private void GetOutput(object parameters)

        {

            List<object> paramList = (List<object>)parameters;

 

            StreamReader sr = (StreamReader)paramList[0];

            StringBuilder sb = (StringBuilder)paramList[1];

 

            try

            {

                while (!sr.EndOfStream)

                {

                    string l = sr.ReadLine();

                    lock (sb)

                    {

                        sb.AppendLine(l);

                    }

                    Console.WriteLine(l);

                }

            }

            // ObjectDisposedException happens if we timeout and close the

            // stream before sr.EndOfStream is reached

            catch (ObjectDisposedException) { }        

        }

Theme: Silver is the New Black. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.