Advanced Visual Basic (Visual Studio 2010) - Project 9

Creating a Multi-Threaded Application

Project 9

What are Threads and what is a multi-Threaded application?

"The Windows operating system separates processes that different applications are executing into processing units called Threads. Threads are the basic unit to which the operating system allocates processor time, and more than one thread can be executing code inside that process. Each thread maintains exception handlers, a scheduling priority, and a set of structures the system uses to save the thread context until it is scheduled. The thread context includes all of the information the thread needs to seamlessly resume execution, including the thread's set of CPU registers and stack, in the address space of the thread's host process."

Don't fall asleep yet, read on...

"An operating system, like Windows, that supports preemptive multitasking creates the effect of simultaneous execution of multiple threads from multiple processes. It does this by dividing the available processor time among the threads that need it, allocating a processor time slice to each thread one after another. The currently executing thread is suspended when its time slice elapses, and another thread resumes running. When the system switches from one thread to another, it saves the thread context of the preempted thread and reloads the saved thread context of the next thread in the thread queue.

"The length of the time slice depends on the operating system and the processor. Because each time slice is small, multiple threads appear to be executing at the same time, even if there is only one processor. This is actually the case on multiprocessor systems, where the executable threads are distributed among the available processors."

Ok, I didn't write the above text (beyond a couple of minor edits), it is taken directly from the .NET help system.  But I think it does a fairly good job of describing in some detail what Threads are all about.  To sum up the above paragraphs:

When an application is run on a single Thread and does any CPU intensive processing, the program will seem to freeze while that processing is happening.  When that same application has the code that does the CPU intensive processing moved to another Thread—leaving the main application on a separate Thread—the CPU intensive processing happens in the background while the application continues to work normally, without interruption.

In this project we will create a standard single-threaded application with several procedures that are very CPU intensive, evaluate it's performance, then convert it into a multi-threaded application.  Then we will evaluate it's performance again, and make comparisons.

Creating the My Calculator project

Launch Microsoft Visual Studio 2010.  Drop down the File menu and select New Project...

Be sure the Windows Forms Application template in the New Project dialog is selected in the Templates pane on the right side, then type MyCalculator in the Name textbox (as shown below): 

Now click the OK button. 

Save the project by clicking on the Save All button on the standard toolbar.  This displays the Save Project dialog box, as shown:

Do not change the default Location path.  Be sure to uncheck the Create directory for solution option, as show above, before clicking on the Save button. 

This creates a new folder inside the My Documents\Visual Studio 2010\Projects\  folder named MyCalculator:

        My Documents\Visual Studio 2010\Projects\MyCalculator

Rename the Form file

Make sure the form file is selected in the Solution Explorer window:

With the form file (Form1.vb) selected in the Solution Explorer window (as shown above), the Properties window directly below it displays it's File properties.  Click on the File Name property and type frmMyCalculator.vb and press the enter key (don't forget to include the .vb extension, as shown in the illustration below):

Change the Name and Text properties of the Form

To display the properties of the form in the Properties window, click once on the blank Form, which should be displayed on the left side of the screen in the Design window.  Make sure the Name property—which is in parentheses (Name) at the top of the property list so that it's easy to find—is frmMyCalculator.  It should have been set to that automatically when we named the form file.  Then scroll the properties windows down—the properties are listed alphabetically—and change the Text property to My Calculator as shown below:

Creating a standard Single-Threaded application - My Calculator

We will create a simple calculator application with six functions.  The textbox at the top of the form (see the illustration below) is where the user types a Number that all the functions use.  Here is a summary of the function that My Calculator will have:

 

Use the illustration above to guide you, as you add a Label and Textbox to the top of the form.  Then add six Groupboxes that each contain one Label and a Button.  And lastly add an exit Button at the bottom of the form.  Set their properties as outlined in the following tables.  Note: Be sure to set the AutoSize properties of all the Labels to False so that you can resize them:

For the Label and Textbox at the top of the form:

Property Label Value Property TextBox Value
Text Number: Name txtNumber
TextAlign MiddleRight Text (blank)

For the Total Primes and Sum of Even Divisors groupboxes and their contents:

Property GroupBox Value Property GroupBox Value
Text Total Primes Text Sum of Even Divisors
Property Label Value Property Label Value
Name lblTotalPrimes Name lblSumEvenDiv
AutoSize False AutoSize False
BackColor White BackColor White
BorderStyle FixedSingle BorderStyle FixedSingle
Text (Blank) Text (Blank)
TextAlign MiddleCenter TextAlign MiddleCenter
Property Button Value Property Button Value
Name btnTotalPrimes Name btnSumEvenDiv
Text Calculate Text Calculate

For the Logarithm and Squared groupboxes and their contents:

Property GroupBox Value Property GroupBox Value
Text Logarithm Text Squared
Property Label Value Property Label Value
Name lblLogarithm Name lblSquared
AutoSize False AutoSize False
BackColor White BackColor White
BorderStyle FixedSingle BorderStyle FixedSingle
Text (Blank) Text (Blank)
TextAlign MiddleCenter TextAlign MiddleCenter
Property Button Value Property Button Value
Name btnLogarithm Name btnSquared
Text Calculate Text Calculate

For the Square Root and Divided by 2 groupboxes and their contents:

Property GroupBox Value Property GroupBox Value
Text Square Root Text Divided by 2
Property Label Value Property Label Value
Name lblSquareRoot Name lblDivideBy2
AutoSize False AutoSize False
BackColor White BackColor White
BorderStyle FixedSingle BorderStyle FixedSingle
Text (Blank) Text (Blank)
TextAlign MiddleCenter TextAlign MiddleCenter
Property Button Value Property Button Value
Name btnSquareRoot Name btnDivideBy2
Text Calculate Text Calculate

Lastly, add an Exit button at the bottom of the form.

Coding the Total Primes function of My Calculator

Prime numbers are integers that are not evenly divisible--that is divisible without a remainder—by any other integers except 1 and themselves.  So the method to determine if a number is NOT a Prime number is to divide it by every integer between 2 and the number and test whether any of the divisions do not yield a remainder.

Visual Basic comes with a Mod operator that divides one value by another and returns the remainder.  The code to use it looks like this:

If Num1 Mod Num2 = 0 Then

The above line of code divides Num1 by Num2 and returns the remainder.  In the case of Prime numbers, if Num1 and Num2 are integers and the remainder is zero (0), then Num1 is not a Prime number.  We can use the above line of code in a For-Next loop to test if a number is not a Prime, like this (do not type this code yet):

Dim i As Long
Dim
TotalPrimesFound As Long
'Test whether Num is a Prime or not.  Be sure
'     not to include 1 and Num in the Mod test.
For i = 2 to Num - 1
    'The Mod operator returns the remainder of
    '    the division of Num by i
   
If Num Mod i = 0 Then
        'If no remainder then Num is Not a Prime!
        '    So quit looking by exiting the loop.
      
Exit For
    End If
Next
i
'If i is equal to Num then the loop must have
'    finished without Mod returning 0, so Num
'    is a Prime number.
If i = Num Then
    TotalPrimesFound  += 1
End If

The above code tests if Num is a Prime by Moding it with every integer between, but not including, 1 and itself.  The code above is the basic code required to test if a single integer is a Prime number, but the Total Primes function needs to test all the numbers starting at 3 up to Number and count the ones that are Primes.  To do that, we need to take the code above and move it inside another For-Next, like this (do not type this code yet):

Dim i, Num, TotalPrimesFound As Long
'Determine the number of Prime numbers
'    between 3 and Number.
For Num = 3 to Number - 1
     'Test whether Num is a Prime or not.  Be sure
     '    not to include 1 and Num in the Mod test.
    For i = 2 to Num - 1
         'The Mod operator returns the remainder of
         '    the division of Num by i
        
If Num Mod i = 0 Then
             'If no remainder then Num is Not a Prime!
             '    So quit looking by exiting the loop.
          
Exit For
       End If
    Next
i
     'If i is equal to Num then the loop must have
     '    finished without Mod returning 0. That
     '    means Num is a Prime number.
    If i = Num Then
         TotalPrimesFound  += 1
   
End If
Next
Num

This is not a math class, so if you haven't been following the Prime number commentary so far, don't worry about it.  The result of the Total Primes function isn't really valuable in a practical sense—though some math lovers might find it fascinating.  All we are really trying to create here is a CPU intensive function, and the above code would certainly qualify.  Now type the following final version of this code into the btnTotalPrimes_Click event procedure:

Dim i, Num, Number, TotalPrimesFound As Long
Try
     'Convert the value typed into txtNumber to a Long.
    Number = CLng(txtNumber.Text)
     'This is going to take a while, so let the user know by
     '    changing the text in lblTotalPrimes to tell them
     '    that the process is underway.
    lblTotalPrimes.Text = "Computing..."
     'Force the label's text property to update by calling
     '    it's Refresh method.
    lblTotalPrimes.Refresh()
     'Disable the button so they can't click on it while
     '    processing is underway.
    btnTotalPrimes.Enabled = False
     'Determine the number of Prime numbers
     '    between 3 and Number.
    For Num = 3 to Number - 1
           'Test whether Num is a Prime or not.  Be sure
           '    not to include 1 and Num in the Mod test.
        For i = 2 to Num - 1
                'The Mod operator returns the remainder of
                '    the division of Num by i. 
            If Num Mod i = 0 Then
                     'If no remainder then Num is Not a Prime!
                     '    So quit looking by exiting the loop.
                Exit For
            End If
        Next i
          'If i is equal to Num then the loop must have
          '    finished without Mod returning 0. That
          '    means Num is a Prime number.
        If i = Num Then
           
TotalPrimesFound  += 1

        End If
    Next Num
Catch
     'Display message if the conversion of txtNumber into a Long fails.
    MessageBox.Show("Please enter a Number value!",  _
          
 "Invalid Number", MessageBoxButtons.OK,  _
           
MessageBoxIcon.Exclamation)
End Try
'Display the total primes found and enable the button.
lblTotalPrimes.Text = TotalPrimesFound
btnTotalPrimes.Enabled = True

In this final version of the code for the btnTotalPrimes_Click event procedure a Try-Catch error trapping structure is a good idea in case the user types an invalid value into the txtNumber textbox.

Testing the program so far

Save the project now.  Run the program.  Type 15000 into the Number textbox and click the Calculate button in the Total Primes groupbox.  You should see the message Calculating... appear for a few seconds in the label in the Total Primes groupbox.  You should then see 1753 as the result (see example below).  This shows that there are 1753 prime numbers between 1 and 15000, which means that 11.69% of the numbers between 1 and 15000 are Primes—only something a math lover would find fascinating:

Coding the Sum of Even Divisors function of My Calculator

What are called Perfect numbers are integers that have the unique quality that the sum of their even divisors are equal to the number.  For example 6 is a Perfect number because 6 is evenly divisible by 1, 2, and 3.  And (1 + 2 + 3) = 6.  Note: The first four perfect numbers are 6, 28, 496, and 8128—these first four perfect numbers were the only ones known to the ancient Greeks.  The computations required to determine if a large number is Perfect are extensive.  So much so in fact, that up until the year 1985 there were only 15 known Perfect numbers.  But with the advancements of computer technology, by the year 2001 at total of 38 Perfect numbers have been found.  The 38th Perfect number contains over 4 million digits!  

Our Sum of Even Divisors function will add up all the integers that evenly divide into Number.  Whenever that sum is equal to Number, then we have found a Perfect number.  Don't expect to find the 39th Perfect number with our little program!  The basic code for the Sum of Even Divisors function will look like this (don't type this code yet):

Num = CLng(txtNumber.Text)
For
i = 1 To Num
- 1
     'If i divides into Num without a
     '    remainder, it is an even number.
    If Num Mod i = 0 Then
          'Add all the even divisors together
        TotalSumEvenDivisors += i
    End If
Next
i

This code does not require a nested loop like the Total Primes function.  So this function will not be as CPU intensive.  Type the following final version of the Sum of Even Divisors code into the btnSumEvenDiv_Click event procedure:

Dim i, Num, TotalSumEvenDivisors As Long
Try
     'Convert the value typed into txtNumber
     '    to a Long.
    Num = CLng(txtNumber.Text)
   
'This is going to take a while, so let the user know
     '    by changing the text in lblSumEvenDiv to tell
     '    them that the process is underway.
    lblSumEvenDiv.Text = "Computing..."
     'Force the label's text property to update by
     '    calling it's Refresh method.
    lblSumEvenDiv.Refresh()
   
btnSumEvenDiv.Enabled = False
    For
i = 1 To Num - 1
        If
Num Mod i = 0 Then
           
TotalSumEvenDivisors += i
        End If
    Next
i
Catch
     'Display message if the conversion of
     '    txtNumber into a Long fails.
    MessageBox.Show("Please enter a Number value!",  _
          
 "Invalid Number", MessageBoxButtons.OK,  _
           
MessageBoxIcon.Exclamation)
End Try
'Display the sum of even divisors and enable the button.
lblSumEvenDiv.Text = TotalSumEvenDivisors
btnSumEvenDiv.Enabled = True

Testing the program so far

Save the project now.  Run the program.  Type 15000 into the Number textbox and click the Calculate button in the Sum of Even Divisors groupbox.  The Calculating... message will appear so briefly in the label in the Sum of Even Divisors groupbox that you may not even see it.  You should then see 31860 as the result (see example below).  This shows that the sum of all the even divisors is a value larger than Number

To test the theory that this function will also help us find Perfect numbers, type 8128 into the Number textbox and click the Calculate button in the Sum of Even Divisors groupbox.  The result this time should be 8128, because 8128 is a Perfect number.

Coding the Remaining functions of My Calculator

The Logarithm, Squared, Square Root, and Divide by 2 functions of My Calculator are relatively simple and require only a single line of code each.  Add the following code to the specified event procedure:

Add this code to the  btnLogarithm_Click event procedure:

Try
     'Use Math.Log to get the Logarithm of Number
    lblLogarithm.Text = _
            Math.
Log(CDbl(txtNumber.Text))
Catch
     'Display message if the conversion of txtNumber into
     '    a Double fails.
    MessageBox.Show("Please enter a Number value!",  _
          
 "Invalid Number", MessageBoxButtons.OK,  _
           
MessageBoxIcon.
Exclamation)
End Try

Add this code to the  btnSquared_Click event procedure:

Try
     'Number * Number is Number squared
    lblSquared.Text = CDbl(txtNumber.Text) *  _
                                   CDbl(
txtNumber.Text)

Catch
     'Display message if the conversion of txtNumber into
     '    a Double fails.
    MessageBox.Show("Please enter a Number value!",  _
          
 "Invalid Number", MessageBoxButtons.OK,  _
           
MessageBoxIcon.
Exclamation)
End Try

Add this code to the  btnSquareRoot_Click event procedure:

Try
     'Use Math.Sqrt to get the Square Root of Number
    lblSquareRoot.Text = _
            Math.
Sqrt(CDbl(txtNumber.Text))
Catch
     'Display message if the conversion of txtNumber into
     '    a Double fails.
    MessageBox.Show("Please enter a Number value!",  _
          
 "Invalid Number", MessageBoxButtons.OK,  _
           
MessageBoxIcon.
Exclamation)
End Try

Add this code to the  btnDivideBy2_Click event procedure:

Try
     'Divide Number by 2
    lblDivideBy2.Text = CDbl(txtNumber.Text) / 2
Catch
     'Display message if the conversion of txtNumber into
     '    a Double fails.
    MessageBox.Show("Please enter a Number value!",  _
          
 "Invalid Number", MessageBoxButtons.OK,  _
           
MessageBoxIcon.
Exclamation)
End Try

Evaluating the finished My Calculator program as a Single-Threaded application

Save the project, and then run it.  Type 35000 (thirty-five thousand) into the Number textbox and click on the Calculate button in the Total Primes groupbox.  You should see the message Calculating... appear.  Now immediately try to click on the Calculate button of the Sum of Even Divisors function.  Try to click any of the other function buttons.  You cannot while the Total Primes processing is happening!  This is because if your program runs on only a single Thread, processor-intensive calculations such as the Total Primes function will tie up the program until the calculations have been completed.   In the next part of this project we will add multi-threading capability to the program so that the processor-intensive code is run on separate threads, to leave our program responding normally.

Converting My Calculator to a Multi-Threaded application

In a multi-threaded application, multiple tasks can be preformed simultaneously.  So moving the most processor-intensive code to separate threads is our objective.  In a program like My Calculator, it is easy to determine which code is the most processor-intensive.  Code that uses nested loops is always suspect.  But code that does any kind of looping can become processor-intensive if the looping goes on for too long.  Our goal is to take the code from the Total Primes and Sum of Even Divisors functions and execute them on their own Threads.  So that the responsiveness of our main program will not be affected while the code from those processor-intensive functions are executing in the background. 

When we execute code on another Thread, we need a way of knowing when that code has finished.  One efficient way of doing this is to create custom event procedures that are raised when the code running on different threads has finished.  To do this, we will need to move the code from the Total Primes and Sum of Even Divisors functions to a Component file.  We've seen in the last project that adding Properties and Methods to a Component is not hard at all.  Adding Event Procedures to a Component file is also not difficult, as we shall see.

Adding a Component file to the My Calculator project

Right-clicking on the MyCalculator project in the solution explorer window and select Add Component... from the Add fly-out menu.  On the Add New Item dialog, with the Component Class template selected in the Templates pane, be sure to change the name from the default Component1.vb to ThreadCode.vb, as shown below, and then click the Add button:

Take a look in your solution explorer window and you should now see a new ThreadCode.vb component file as part of the project (as shown below):

Moving the Total Primes function to the  ThreadCode component file

Ideally we would just pass Number to a TotalPrimes function that we create in the component file, and it would return the result of the calculation.  The declaration of that function would look like this (do not type this code):

Public Function TotalPrimes(ByVal Number As Long) As Long

However arguments cannot be passed between threads, nor can values be returned!  This is a limitation of threads and we will have to work around it: 

Go to the code view of the ThreadCode.vb component file, and add the follow two lines of code to the Declaration section:

'Number will be set to the value of txtNumber.Text
'    prior to executing the threaded code as a way of
'    passing Number across Thread boundaries.
Public Number As Long
'Create an event procedure that will be raised when
'    the TotalPrimes calculation has completed.
Public Event TotalPrimesComplete( _
            ByVal
TotalPrimesFound As Long)

The first line of code above creates a public Number variable in the component file.  This variable will be accessible from the frmMyCalculator file.  By setting Number equal to the value of txtNumber.Text prior to executing any code in the component file that will run on separate threads, we can get around the limitation that prevents values from being passed as arguments to functions across threads boundaries.  The second line of code above, creates a custom TotalPrimesComplete event procedure.  We will raise this event procedure when the Total Primes function finishes and pass the result of the calculation as the TotalPrimesFound argument to this event.

Instead of adding a TotalPrimes function to the component file—which would be useless since we cannot pass arguments to it or return values from it across thread boundaries—we will add a TotalPrimes method.  With your cursor on the line above the End Class statement in the code window of ThreadCode.vb, type this code for the TotalPrimes method:

Public Sub TotalPrimes()
    Dim
i, Num, TotalPrimesFound As Long
    For
Num = 3 To Number - 1
        For
i = 2 To Num - 1
            If
Num Mod i = 0 Then
                Exit For
            End If
        Next
i
        If
i = Num Then
           
TotalPrimesFound += 1
        End If
    Next
Num

   
'When the looping is finished, raise the TotalPrimesComplete
     '    event and pass TotalPrimesFound as an argument.
    RaiseEvent TotalPrimesComplete(TotalPrimesFound)
End Sub

The above code is pulled directly from the code in the btnTotalPrimes_Click event procedure of frmMyCalculator.vb.  Notice the last line which raises the TotalPrimesComplete event procedure that we declared in the Declarations section of ThreadCode.vb

Getting the TotalPrimes method to run on a separate Thread

To make the TotalPrimes method run on a separate thread we need to declare a Thread class reference variable that we can use to construct an instance of a Thread.  Add the following line of code to the Declarations section of ThreadCode.vb:

'Declare a TotalPrimesThread thread class reference variable
Public TotalPrimesThread As System.Threading.Thread

When we actually construct an instance of the TotalPrimesThread thread, we will need to specify a ThreadStart object, which points to the address of the procedure where the thread is to begin execution.  The code to create the thread, and start it running looks like this (don't type this code yet):

'Construct an instance of the TotalPrimesThread
'    and specify the procedure (AddressOf
'    TotalPrimes) to be executed on the thread.
TotalPrimesThread =  _
   
New System.Threading.Thread( _
               AddressOf
TotalPrimes
)
'Start the Thread.
TotalPrimesThread.Start()

The first line of code above constructs an instance of the TotalPrimesThread thread.  Important: When constructing an instance of a thread, we must specifying the address of the procedure that is to be executed on this thread, i.e. AddressOf TotalPrimes.  This code creates the Thread, but doesn't actually start it running, until we execute the Start method of the thread on the second line above.  We will consolidate the code above inside a new method that can be called from the btnTotalPrimes_Click event procedure of frmMyCalculator.vb.   Add the following code to the ThreadCode.vb component file to create a new RunTotalPrimesThread method:

Public Sub RunTotalPrimesThread()
     'Construct an instance of the TotalPrimesThread
     '    and specify the procedure (AddressOf
     '    TotalPrimes) to be executed on the thread.
    TotalPrimesThread =  _
           
New System.Threading.Thread( _
                      
AddressOf TotalPrimes
)
   
'Start the Thread.
    TotalPrimesThread.Start()
End Sub

Here is a summary of all the code you've added to the ThreadCode.vb component file so far:

From the Declarations section:

'Number will be set to the value of txtNumber.Text prior
'    to executing the threaded code as a way of passing
'    Number across Thread boundaries.
Public Number As Long
'Create an event procedure that will be raised when the
'    TotalPrimes calculation has completed.
Public Event TotalPrimesComplete( _
       
ByVal TotalPrimesFound As Long)
'Declare a TotalPrimesThread thread class reference
'    variable.
Public TotalPrimesThread As System.Threading.Thread

From below the Code section:

Public Sub TotalPrimes()
    Dim
i, Num, TotalPrimesFound As Long
    For
Num = 3 To Number - 1
        For
i = 2 To Num - 1
            If
Num Mod i = 0 Then
                Exit For
            End If
        Next
i
        If
i = Num Then
           
TotalPrimesFound += 1
        End If
    Next
Num
   
'When the looping is finished, raise the TotalPrimesComplete
     '    event and pass TotalPrimesFound as an argument.
    RaiseEvent TotalPrimesComplete(TotalPrimesFound)
End Sub

Public Sub RunTotalPrimesThread()
     'Construct an instance of the TotalPrimesThread and
     '    specify the procedure (AddressOf TotalPrimes) to
     '    be executed on the thread.
    TotalPrimesThreadNew _
        System.
Threading.Thread(AddressOf TotalPrimes)
   
'Start the Thread.
    TotalPrimesThread.Start()
End Sub

Make sure that your code matches the above code.  Here is an illustration that should match your ThreadCode.vb code view:

Save the project before continuing.  DO NOT try to test run your project yet!

Now we are ready to make the modifications that are necessary to the frmMyCalculator.vb file to use the new TotalPrimes method of the ThreadCode.vb component file.  The first thing we need to do, is declare an instance of the ThreadCode component, so that we can access it's methods and event procedures from frmMyCalculator.vb.  Add this dim statement to the Declaration section of frmMyCalculator.vb to construct an instance of a ThreadCode component:

Dim WithEvents ThreadCode1 _
        As
New
ThreadCode()

By using the WithEvents specification, any event procedures that ThreadCode contains will be made available in the method list when we selected the ThreadCode1 class in the code view. 

First we will modify the code in the btnTotalPrimes_Click event procedure to use the ThreadCode component's TotalPrimes method.  Change the existing code in the btnTotalPrimes_Click event procedure so that it looks like this (don't just add this as new code, modify the existing code to look like this!):

Try
     'Get around the limitation that prevents the passing
     '    of arguments between threads by setting the
     '    component's public Number variable to the value
     '    of txtNumber.Text.
    ThreadCode1.Number = CLng(txtNumber.Text)
   
lblTotalPrimes.Text = "Computing..."
   
lblTotalPrimes.Refresh()
   
btnTotalPrimes.Enabled = False
     'Call the component's RunTotalPrimesThread
     '    method to start executing the TotalPrimes
     '    method on a separate thread.
    ThreadCode1.RunTotalPrimesThread()
Catch
     'Display message if the conversion of
     '    txtNumber into a Long fails.
    MessageBox.Show("Please enter a Number value!",  _
          
 "Invalid Number", MessageBoxButtons.OK,  _
           
MessageBoxIcon.Exclamation)
End Try

The above code starts the TotalPrimes method of the ThreadCode component running on a separate thread.  But what about the result of the function?  This is where the TotalPrimesComplete event procedure we created in the ThreadCode.vb component file comes in handy.   Drop down the Class Name list on the top left of the code window of frmMyCalculator.vb and choose the ThreadCode1 class, as shown:

Now drop down the Methods list on the top right of the code window and choose the event procedure we just created, TotalPrimesComplete, as show below:

Your cursor should now be flashing inside your custom TotalPrimesComplete event procedure, waiting for you to add code to it, like this:

Here is were we put the code—that used to be at the bottom of the old version of our btnTotalPrimes_Click event procedure—to assign the result of the TotalPrimes function to the lblTotalPrimes label.  Add the following code to your custom ThreadCode1_TotalPrimesComplete event procedure:

'The results of the TotalPrimes method are
'    passed to this event procedure in the
'    TotalPrimesFound argument.
lblTotalPrimes.Text = TotalPrimesFound
'Enable btnTotalPrimes now that
'    processing is complete.
btnTotalPrimes.Enabled = True

Note: To avoid an erroneous threadsafe exception error which is caused by setting the Text property of lblTotalPrimes with the TotalPrimesFound variable—which is passed from our ThreadCode thread—add the following line of code to the frmMyCalculator_Load event procedure:

CheckForIllegalCrossThreadCalls = False

The Load event procedure of your form should look like this:

That should do it.  But before we test run the program, let me summarize what we just did:

What we did in the ThreadCode.vb component file:

  1. We moved the code that calculates the total primes from the btnTotalPrimes_Click event procedure to a TotalPrimes Method in a new ThreadCode.vb component file.
  2. We added a custom TotalPrimesComplete event procedure to the ThreadCode.vb component file that is raised when the code in the TotalPrimes method has finished.
  3. We added a RunTotalPrimesThread Method to the ThreadCode.vb component file that constructs an instance of a new thread that uses the AddressOf the TotalPrimes Method as it's execution starting point and starts the thread running.

What we did in the frmMyCalculator.vb form file:

  1. We changed the code in the btnTotalPrimes_Click event procedure to include a call to the ThreadCode1 component's  RunTotalPrimesThread Method which starts executing the TotalPrimes method on a separate thread.
  2. We added the code to the TotalPrimesComplete event procedure (which is raised after the total primes calculation is finished) that copies the result of the TotalPrimes method to the lblTotalPrimes label, and enables the btnTotalPrimes button.

In summary, we took the Total Primes processor-intensive code and put into a method that is run on a separate thread from the main program.  The theory here is that the main program will continue to run unimpeded while the processor-intensive code is chugging away on a separate thread in the background.  Let's see if it works!

Testing the program so far

Save the project, and then run the program.  Type 35000 (thirty-five thousand) into the Number textbox and click on the Calculate button in the Total Primes groupbox.  You should see the message Calculating... appear.  Now immediately try to click on the Calculate button of the Sum of Even Divisors function.  Did it respond right away, while the Total Primes calculate is still going?  By executing the processor-intensive code of the Total Primes function on a separate thread, our program doesn't have to wait for it to finish before we can interact with the other controls on the form.

Moving the Sum of Even Divisors function to a separate Thread

We're not finished yet.  The Sum of Even Divisors function is also a potential problem if the user types a large enough number into the Number textbox.  If you don't believe me, try typing 100000 into the Number textbox and click the calculate button in the Sum of Even Divisors groupbox. 

Try to do it on your own

Use the same process we applied to the Total Primes code to get the Sum of Even Divisors code to run on a separate thread.  Here are some hints:

What you'll need to add to the ThreadCode.vb component file:

What you'll need to add to the frmMyCalculator.vb form file:

If you get stuck

Only refer to the follow example code if you get stuck:

Add the following code to the Declarations section of ThreadCode.vb:

'Create an event procedure that will be raised when the
'    SumOfEvenDivisors calculation has completed.
Public Event SumOfEvenDivisorsComplete(ByVal _
        TotalSumOfEvenDivisors
As Long)
'Declare a SumOfEvenDivisorsThread thread class
'    reference variable.
Public SumOfEvenDivisorsThread As _
        System.
Threading.Thread

Insert the following code below the code in the Declarations Section and above the first Public Sub line:

Public Sub SumOfEvenDivisors()
    Dim
i, TotalSumOfEvenDivisors As Long
    For
i = 1 To Number - 1
        If
Number Mod i = 0 Then
   
        TotalSumOfEvenDivisors += 1
        End If
     Next
i
  
  'When the looping is finished, raise the
    
'    SumOfEvenDivisorsComplete event and pass
    
'    TotalSumOfEvenDivisors as an argument.
    RaiseEvent SumOfEvenDivisorsComplete( _
                Total
SumOfEvenDivisors)
End Sub

Public Sub RunSumOfEvenDivisorsThread()
   
 'Create an instance of SumOfEvenDivisorsThread and
     '    specify the procedure (AddressOf SumOfEvenDivisors)
     '    to be executed on the thread.
    SumOfEvenDivisorsThread =  _
           
New System.Threading.Thread( _
                    AddressOf
SumOfEvenDivisors)
   
'Start the Thread.
    SumOfEvenDivisorsThread.Start()
End Sub

Change the code to the btnSumEvenDiv_Click event procedure of frmMyCalculator.vb to this:

Try
     'Get around the limitation that prevents the passing arguments
     '    between threads by setting the component's public Number
     '    variable to the value of txtNumber.Text.
    ThreadCode1.Number = CLng(txtNumber.Text)
   
lblSumEvenDiv.Text = "Computing..."
   
lblSumEvenDiv.Refresh()
   
btnSumEvenDiv.Enabled = False
     'Call the component's RunSumOfEvenDivisorsThread method to start
     '    executing the SumOfEvenDivisors method on a separate thread.
    ThreadCode1.RunSumOfEvenDivisorsThread()
Catch
     'Display message if the conversion of txtNumber into a Long fails.
    MessageBox.Show("Please enter a Number value!",  _
          
 "Invalid Number", MessageBoxButtons.OK,  _
           
MessageBoxIcon.Exclamation)
End Try

Add this code to the new ThreadCode1_SumOfEvenDivisorsComplete event procedure of frmMyCalculator.vb:

'The results of the SumOfEvenDivisors method are passed to this
'    event procedure in the TotalSumOfEvenDivisors argument.
lblSumEvenDiv.Text = TotalSumOfEvenDivisors
'Enable btnSumEvenDiv now that processing is complete.
btnSumEvenDiv.Enabled = True

One final consideration before the My Calculator program is finished

After making the final changes to get the Sum Of Even Divisors function running on it's own thread, we are almost finished.  As you might have discovered from testing this project with very large numbers, if the number is big enough, the Total Primes and Sum Of Even Divisors functions can take several minutes to finish processing.  What happens if the user decides they are not going to wait, and they exit the program before the processes that are running on separate threads are finished?  If the user terminates our My Calculator program while the Total Primes thread is still processing, it will be left in memory as an orphaned thread! This may not cause a problem—the Total Primes processing will eventually finish and the thread will terminate.  But if they try to shut down their computer while the thread is still running, they will receive a cryptic error message about a process that must be terminated.  To prevent that potential inconvenience we can abort our threads if they are still running when the My Calculator program exits.

Aborting any running Threads when the My Calculator program exits

Be sure that the following code is in the btnExit_Click event procedure:

Me.Close()

This will make sure that the FormClosing event procedure of the form is executed as the program terminates.  Now add the following code to the frmMyCalculator_FormClosing event procedure:

'As the program exits check if either thread
'    IsAlive and Abort them if needed.
If ThreadCode1.TotalPrimesThread.IsAlive = _
       
True Then
  
 ThreadCode1.TotalPrimesThread.Abort()
End If
If
ThreadCode1.SumOfEvenDivisorsThread.IsAlive = _
        
True Then
  
 ThreadCode1.SumOfEvenDivisorsThread.Abort()

End If

Evaluating the finished My Calculator program as a Multi-Threaded application

Make sure you save the project first and then try it out.  You should discover that by running the Total Primes and Sum Of Even Divisors functions on separate threads, the program doesn't need to wait for those processes to finish before you can continue using the other functions on the form.  In fact, if you type a large enough number into the Number textbox and click the Total Primes and Sum Of Even Divisors calculate buttons, you can even type a new number in the Number textbox and still use the other function buttons to process the new number while the processor-intensive Total Primes and Sum Of Even Divisors functions are still processing the original number in the background.


To copy a Project folder from your Projects folder on the Hard Drive to a floppy diskette or pen-drive follow these steps:

  1. Exit Visual Studio 2010 and insert the floppy diskette or pen-drive, that you want to copy the MyCalculator folder to:
  2. Select the My Documents item on the Start Menu to open the My Documents folder.
  3. In the My Documents folder, double-click the Visual Studio 2010 folder to open it.
  4. Double-click on your Projects folder to open it.
  5. Open the MyCalculator folder by double-clicking on it.  Inside the MyCalculator folder, delete the Obj and Bin folders—these folders are created automatically when you open a project.  You do not need to copy them, or their contents, to your floppy diskette or pen-drive.  Important: Be sure not to delete the My Project folder or Resources folder.
  6. Once you have deleted the Obj and Bin folders, hit the Backspace key once—or click the Back button on the toolbar.  This moves you from inside the MyCalculator folder to back inside your Projects folder.
  7. Right-click on the MyCalculator folder and selected: 31/2" Floppy A: or your pen-drive on the Send To fly-out menu.  This copies the MyCalculator folder to your floppy diskette or pen-drive.