Launching a process from Windows Forms

Page 2 of 3
  1. Introduction
  2. Creating a class to call a process
  3. Wrapping Up

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.

You might also like...

Comments

About the author

Mike Mayer United States

Mike Mayer is a Microsoft MVP in Visual C#.

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Anyone who considers arithmetic methods of producing random digits is, of course, in a state of sin.” - John von Neumann