Click here to Skip to main content
Click here to Skip to main content

Launching a process and displaying its standard output

, 31 Aug 2003
Rate this:
Please Sign up or sign in to vote.
This article shows how to launch a process (such as a bat file, perl script, console program) and have its standard output displayed on a windows form.

Sample Image - LaunchProcess.png

Introduction

I wanted to launch a script from a Windows Form application and display the standard output in a text box as the process was running.  Surely you're not surprised to learn that multithreading is involved.  It turns out you'll have at least four threads running to do this simple task.  To keep things simple and sane, I've reused code from other another source, so I must first give credit to the MSDN article "Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads"  by I.D. Griffiths.  I highly suggest reading this article for more background on multithreading in Windows Forms applications.  Thanks also to Chad Christensen for his suggestions in using a RichTextBox.

Creating a class to call a process

A script or executable can be run using System.Diagnostics.Process.  The string FileName is set to the executable (e.g. perl.exe, Run.bat, ConsoleApplication.exe).  The string Arguments is set to the command-line arguments for that executable (e.g. perlscript.pl, filename1.txt filename2.txt, etc).  The following code will start that executable.

    Process process = new Process();
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.CreateNoWindow = true;
    process.StartInfo.FileName = FileName;
    process.StartInfo.Arguments = Arguments;
    process.StartInfo.WorkingDirectory = WorkingDirectory;
    process.Start();

Notice that the standard output and standard error have both been redirected.  There are two StreamReaders in the Process class that can be used to read the output: Process.StandardOutput and Process.StandardError.  Often, the output is not read until after the process has finished, as in the following:

    string output = process.StandardOutput.ReadToEnd();

Reading to the end will not work for this application, since we want to read the data as the process is running.

Multiple threads

The solution is to use multiple threads.  One thread is dedicated to running the process and two more threads are dedicated to reading the standard error and standard output.  This is mentioned in MSDN documentation.  Each of these two threads will run a simple function that sits in a loop reading from the stream until the stream is closed.

    void ReadStdOut()
    {
        string str;
        while ((str = process.StandardOutput.ReadLine()) != null)
        {
            // do something with str
        }
    }

After each line is read into str, we would like to notify a windows form to display the text.  Raising an event is probably the best way to accomplish this.  For every new line of text received (on either StandardOutput or StandardError) an event will be raised.  A windows form class can subscribe to these events and update a text box.  Simple, but it won't quite work without some additional work.

Important rule of windows forms

There is an important rule of windows forms and multithreading.  Controls are (almost entirely) not thread safe.  This means that an event raised from any thread other than the UI Thread cannot use methods or properties of a control.  There are a few methods guaranteed to be safe including Control.Invoke and Control.BeginInvoke.  These methods are used to run a function on the UI thread. 

Thankfully, we can inherit from the class AsyncOperation (written by I.D. Griffiths from the above mentioned MSDN article) to solve several problems.  First, this class allows us to raise an event on a UI thread of a hosting or target control.  The above function becomes:

    public delegate void DataReceivedHandler(object sender,
        DataReceivedEventArgs e);

    public event DataReceivedHandler StdOutReceived;

    void ReadStdOut()
    {
        string str;
        while ((str = process.StandardOutput.ReadLine()) != null)
        {
            FireAsync(StdOutReceived, this, new DataReceivedEventArgs(str));
        }
    }

FireAsync is a method provided by the class AsyncOperation.  It raises an event (or more specifically invokes any delegate) on the UI thread of a form or control.  StdOutReceived is the event that will be raised.  DataReceivedEventArgs is a class derived from EventArgs that has a single string containing the text to be displayed (its definition is not shown here for brevity).

The second thing AsyncOperation provides is a method of canceling a process.  Let's take a look at that class in more detail.

Inheriting from AsyncOperation

AsyncOperation is an abstract base class that assists in creating cancelable worker threads that can fire events back on a UI control (or form).  It provides two main methods that are called by a form class: Start() and Cancel().

AsyncOperation requires that one method be overridden: DoWork(). This method is called when the Start() method is called.  As the method runs, it is expected to watch for a flag CancelRequested that is set from a call to Cancel().  If the flag is true, the method should acknowledge the cancel and return.

The implementation of DoWork() is as follows:

    void protected override void DoWork()()
    {
        // Start a new process for the cmd
        Process process = new Process();
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.FileName = FileName;
        process.StartInfo.Arguments = Arguments;
        process.StartInfo.WorkingDirectory = WorkingDirectory;
        process.Start();

        
        // Invoke stdOut and stdErr readers - each
        // has its own thread to guarantee that they aren't
        // blocked by, or cause a block to, the actual
        // process running (or the gui).
        new MethodInvoker(ReadStdOut).BeginInvoke(null, null);
        new MethodInvoker(ReadStdErr).BeginInvoke(null, null);

        // Wait for the process to end, or cancel it
        while (! process.HasExited)
        {
            Thread.Sleep(SleepTime); // sleep
            if (CancelRequested)
            {
                // Not a very nice way to end a process,
                // but effective.
                process.Kill();
                AcknowledgeCancel();
            }
        }

    }

The methods DoWork()ReadStdOut(), and ReadStdErr(), the properties FileName and Arguments, and the events StdOutReceived and StdErrReceived are all added to a class ProcessCaller which derives from AsyncOperation.  Both classes can be downloaded as part of the zipfile at the top of the page.

The form

As shown in the picture above, the form is quite simple.  It consists of a rich text box to show the standard output and standard input, a button to run a process (Ok), and a button to cancel the process (Cancel).

The Ok button calls the Start() method on ProcessCaller and the Cancel button calls the Cancel() method.  The events StdOutRecieved and StdErrReceived are handled by the following function:

    private void writeStreamInfo(object sender, DataReceivedEventArgs e)
    {
        this.richTextBox1.AppendText(e.Text + Environment.NewLine);
    }

Improvements

Adding a progress bar to the form is one nice improvement for the user interface.  Of course, you have to know the progress of the program being run.  One option is to have the script tell you explicitly in the standard output with lines such as: "Percent completion = 30".  Your "writeStreamInfo" function would filter those lines and update a progress bar.

Standard Error can be displayed in red or some other color (or in a separate rich text box) to highlight any errors found.  At the end of the process, a dialog could be displayed with a list of all errors.

Providing support for standard input shouldn't be too difficult, but integrating it with the windows form may be tough.  Perhaps a separate single-line text box whose contents are sent to the standard input stream through a method on ProcessCaller.

These are just a few ideas of improvements you can make.

Conclusion

Using the class AsyncOperation reduced the design complexity of this program.  Hopefully, the class ProcessCaller will provide you just as much help in reducing the complexity of running scripts and monitoring the output.

Revision History

  • 2003-Jul-31 : Original Post
  • 2003-Aug-05 : Fixed some spelling / grammatical mistakes (oops)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Mike Mayer

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberactualmanx19-Feb-14 4:31 
QuestionFails to catch exceptions from start process Pinmembergrafne7919-Aug-11 0:25 
GeneralDisconnectedContext was detected Errror PinmemberJason Hubbard28-Aug-09 15:48 
GeneralWant to do same in Web forms Pinmembert_bala1822-Jun-09 15:48 
GeneralCaptureConsole.DLL - A Universal Console Output Redirector for all Compilers PinmemberElmue18-Jun-09 13:57 
QuestionSuspending and resuming the process Pinmemberohad-oz21-Nov-08 6:59 
QuestionUsing "redirectstandardinput" with this Pinmemberimmortaldeath19-Sep-08 8:47 
GeneralA bit of a newb here, but figured I would give it a try. Pinmemberimmortaldeath3-Sep-08 18:58 
GeneralAbout the article "Launching a process and displaying the standard output" on Code project PinmemberAnupama Sankaran28-Aug-08 8:58 
QuestionTerminate process on standard error Pinmemberoidele27-Oct-07 10:19 
QuestionGUI blocked PinmemberMeJVZ6-Jul-07 8:17 
GeneralYour code does not work always VERY STRANGE PinmemberElmue14-Dec-06 22:08 
Hello
 
First thanks for the great article.
With your demo application which writes to the console (SmallConsoleProgram.exe) all works fine.
 
But what I want to do is display the output from Mplex.
Mplex is a multiplexer which multiplexes a MPEG file from separate video and audio files.
Mplex writes all its output to the console.
Immediately after Mplex has started it writes the first line of output to the DOS box, which is always this:
 
   INFO: [???] mplex version 2.2.2 ($Date: 2003/05/13 20:27:15 $)
 
The strange thing is that your progressform will display NOTHING while Mplex is running (which may take 10 minutes or more for big video files).
The textbox stays empty all the time.
Not before the Mplex process exited ALL the output appears at once!
 
But when I start Mplex in the DOS box (Cmd.exe) I see that it immediately starts to write its output!
 
How is it possible that your demo works with one console application but not with another one ???
 
Has anyone an explanation ?
 
P.S.
If found another project which does the same but written in C++.
It also displays the output AFTER Mplex has finished!
 
Elmü
____________________________________________
 
This is what I changed to your project:
 
processCaller.FileName  = "C:\\Program Files\\Muxer\\mplex.exe";
processCaller.Arguments = "-f 8 -o \"E:\\Videos\\MyVideo.mpg\" \"E:\\Videos\\MyVideo.m2v\" \"E:\\Videos\\MyVideo.mp2";
 
When the mplex process has finished this appears ALL at once in the GUI:
 
   INFO: [???] mplex version 2.2.2 ($Date: 2003/05/13 20:27:15 $)
   INFO: [???] File E:\Videos\MyVideo.m2v looks like an MPEG Video stream.
   INFO: [???] File E:\Videos\MyVideo.mp2 looks like an MPEG Audio stream.
   INFO: [???] Video stream 0: profile 8 selected - ignoring non-standard options!
   INFO: [???] Found 1 audio streams and 1 video streams
   INFO: [???] Selecting dvdauthor DVD output profile
   INFO: [???] Multiplexing video program stream!
   INFO: [???] Scanning for header info: Video stream e0 (E:\Videos\MyVideo.m2v) 
   INFO: [???] VIDEO STREAM: e0
   INFO: [???] Frame width     : 720
   INFO: [???] Frame height    : 576
   INFO: [???] Aspect ratio    : 4:3 display
   INFO: [???] Picture rate    : 25.000 frames/sec
   INFO: [???] Bit rate        : 15000000 bits/sec
   INFO: [???] Vbv buffer size : 229376 bytes
   INFO: [???] CSPF            : 0
   INFO: [???] Scanning for header info: Audio stream c0 (E:\Videos\MyVideo.mp2)
   INFO: [???] MPEG AUDIO STREAM: c0
   INFO: [???] Audio version  : 1.0
   INFO: [???] Layer          :        2
   INFO: [???] CRC checksums  :       no
   INFO: [???] Bit rate       :    16384 bytes/sec (128 kbit/sec)
   INFO: [???] Frequency      :     48000 Hz
   INFO: [???] Mode           :        1 joint stereo
   INFO: [???] Mode extension :        0
   INFO: [???] Copyright bit  :        1 copyright protected
   INFO: [???] Original/Copy  :        1 original
   INFO: [???] Emphasis       :        0 none
   INFO: [???] SYSTEMS/PROGRAM stream:
   INFO: [???] rough-guess multiplexed stream data rate    : 15359248
   INFO: [???] target data-rate specified               : 10080000
++ WARN: [???] Target data rate lower than computed requirement!
++ WARN: [???] N.b. a 20% or so discrepancy in variable bit-rate
++ WARN: [???] streams is common and harmless provided no time-outs will occur
   INFO: [???] Run-in Sectors = 89 Video delay = 13019 Audio delay = 0
   INFO: [???] New sequence commences...
   INFO: [???] Video e0: buf= 237568 frame=000000 sector=00000000
   INFO: [???] Audio c0: buf=   4096 frame=000000 sector=00000000
++ WARN: [???] Discarding incomplete final frame MPEG audio stream c0!
   INFO: [???] Scanned to end AU 2280
   INFO: [???] STREAM e0 completed @ frame 2280.
   INFO: [???] STREAM c0 completed @ frame 3800.
   INFO: [???] Multiplex completion at SCR=8207506.
   INFO: [???] Video e0: buf= 109094 frame=002280 sector=00022205
   INFO: [???] Audio c0: buf=   1024 frame=003800 sector=00000723
   INFO: [???] VIDEO_STATISTICS: e0
   INFO: [???] Video Stream length:    44771532 bytes
   INFO: [???] Sequence headers:      191
   INFO: [???] Sequence ends   :        1
   INFO: [???] No. Pictures    :     2280
   INFO: [???] No. Groups      :      191
   INFO: [???] No. I Frames    :      191 avg. size 58006 bytes
   INFO: [???] No. P Frames    :      570 avg. size 23285 bytes
   INFO: [???] No. B Frames    :     1520 avg. size 13434 bytes
   INFO: [???] Average bit-rate :  3925600 bits/sec
   INFO: [???] Peak bit-rate    :  5729600  bits/sec
   INFO: [???] BUFFERING min 15 Buf max 134306
   INFO: [???] AUDIO_STATISTICS: c0
   INFO: [???] Audio stream length 1459588 bytes.
   INFO: [???] Syncwords      :     3802
   INFO: [???] Frames         :     3802 padded
   INFO: [???] Frames         :        0 unpadded
   INFO: [???] BUFFERING min 15 Buf max 395
   INFO: [???] MUX STATUS: no under-runs detected.

GeneralRe: I found the reason PinmemberElmue15-Dec-06 4:32 
GeneralRe: I found the reason PinmemberRYU^^1-Mar-07 18:10 
GeneralRe: I found the reason Pinmembercggiriraj17-Mar-08 4:00 
GeneralRe: I found the reason Pinmemberzeiksz18-Jun-09 0:34 
QuestionVS2005 update? Pinmembertaumuon25-Jul-06 7:11 
AnswerRe: VS2005 update? PinmemberPushkar Pathak22-Aug-06 15:55 
GeneralAnother way... PinmemberAlun Evans25-Jul-06 3:37 
Questionstdin solution??? Pinmembersilvepaadmin9-Jun-06 18:13 
GeneralSource code not working with VS 2005 Pinmemberjelewis8-Jun-06 6:29 
AnswerRe: Source code not working with VS 2005 PinmemberClaudia aus Hannover7-Aug-06 21:44 
Generalqueuing jobs Pinmemberbillou_2_k7-Mar-06 8:05 
GeneralMessage order PinmemberSerialHobbyist17-Feb-06 23:17 
GeneralA question about Multi-files' print by Process Pinmemberlovvver20-Jan-06 21:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 1 Sep 2003
Article Copyright 2003 by Mike Mayer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid