Thread Maintenance and Synchronization

So far, you have started threads using the Start method, and they have died naturally. It is also possible to suspend, restart, and kill threads. You can check the states of threads at any time. Threads can also be assigned priorities, which control the size of the time slice they are given by the processor. In addition, threads can be foreground or background threads.

Foreground and Background Threads

Given the current state of your application, there is something interesting you can do with it. If you run the application and click the Pass Params button, the code will execute and two message boxes eventually will be displayed, one from each of the threads you created. When the message boxes are displayed, you can click back on the main form without clearing them because they are running on a different thread from the thread handling the user interface. Now, if you close the main form, the message boxes remain.

Your initial thought might have been that the message boxes would go away. After all, in classic Visual Basic, you could create a component and when you closed the program, all references to the component went away and the component would drop out of memory. Threads, however, are not components. The threads containing the message boxes are still alive, and your application is therefore still running.

The threads that you have created are foreground threads, which is the default for a new thread. Foreground threads run indefinitely, but background threads are killed when the last foreground thread has stopped.

You can see the behavior of foreground and background threads by adding one line to your cmdPassParam_Click sub. Make your code look like this:

Private Sub cmdPassParam_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdPassParam.Click
   Dim count1 As New Counter()
   Dim count2 As New Counter()
   count1.mlCount = 1000000000
   count2.mlCount = 99999999
   Dim threadCounter1 As New System.Threading.Thread(AddressOf count1.Count)
   Dim threadCounter2 As New System.Threading.Thread(AddressOf count2.Count)
   threadCounter1.IsBackground = True
   threadCounter1.Start()
   threadCounter2.Start()
End Sub

The only change here is that you have set the threadCounter1's IsBackground property to True. This means that the thread with the larger mlCount will close when all foreground threads close.

Run the application and click on the Pass Params button. After both threads have displayed the message box, click on the OK button for the message box from threadCounter2 (the one with the lower number of iterations). At this point, you still have the form open and one message box. Close the main form, and you'll notice that the message box closes immediately. This is because the message box was running on a background thread, and all foreground threads in the application have terminated.

Thread Priority

Not all threads are created equal; some threads have higher or lower priorities than other threads. All threads do get a turn at the processor, but some threads get a longer time slice than other threads.

There are five thread priorities:

  • Highest

  • AboveNormal

  • Normal (the default)

  • BelowNormal

  • Lowest

These thread priorities are easy to set: just use the Priority property of the thread method. For example, if you add the following line to your code after you create threadCounter1, and then run the application, you'll see that threadCounter1 now runs much faster.

threadCounter1.Priority = Threading.ThreadPriority.AboveNormal

In fact, on the test machine used for this code, threadCounter1 finished before threadCounter2 after the priority was changed, despite the fact that threadCounter1 runs through the loop more times by a factor of ten.

Thread States

Threads have a variety of states in which they can exist. You can check the state of a thread at any point in time by examining the ThreadState property of a thread. Numerous states are possible for a thread, depending on what methods you have called. For example, when you call the Start method, the thread enters the Running thread state.

It is possible to suspend a thread at any point using either the Sleep or the Suspend method. The Sleep method puts the thread to sleep for a specified period of time, with the time being passed as either an Integer or TimeSpan data type. Sleep puts the thread in the WaitSleepJoin thread state. The Suspend method does not take any arguments, and suspends execution on the thread until the thread is Resumed or Aborted. Suspend puts the thread in the Suspended state. Threads in the WaitSleepJoin state are restarted with the Interrupt method.

The Resume method restarts a thread that has been suspended. After the thread has been resumed, it is back in the Running thread state.

You can check the fact that threads are being suspended and changing thread state by using the following code. Modify the code in your application by changing it to the following:

Dim threadCounter1 As System.Threading.Thread

Private Sub cmdBeep_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdBeep.Click
   Beep()
   MsgBox(threadCounter1.ThreadState.ToString)
   threadCounter1.Resume()
End Sub

Private Sub cmdPassParam_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdPassParam.Click
   Dim count1 As New Counter()
   Dim count2 As New Counter()
   count1.mlCount = 1000000000
   count2.mlCount = 99999999
   threadCounter1 = New System.Threading.Thread(AddressOf count1.Count)
   Dim threadCounter2 As New System.Threading.Thread(AddressOf count2.Count)
   threadCounter1.IsBackground = True
   threadCounter1.Start()
   threadCounter1.Suspend()
   threadCounter2.Start()
End Sub

The first change you have made is to move the definition of the threadCounter1 variable to the module level. Inside of cmdPassParam_Click, you instantiate threadCounter1, start it, and immediately suspend it. The thread will stay suspended indefinitely, so after it is suspended, you can click on the Beep button. The Beep button now first displays the thread state to you in a message box, and then resumes threadCounter1.

You can destroy a running thread, which is something you haven't done. Make the following changes to your code, which just involves removing the Suspend in cmdPassParam_Click and then modifying the cmdBeep_Click sub:

Private Sub cmdBeep_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdBeep.Click
   Beep()
   threadCounter1.Abort()
End Sub

Private Sub cmdPassParam_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdPassParam.Click
   Dim count1 As New Counter()
   Dim count2 As New Counter()
   count1.mlCount = 1000000000
   count2.mlCount = 99999999
   threadCounter1 = New System.Threading.Thread(AddressOf count1.Count)
   Dim threadCounter2 As New System.Threading.Thread(AddressOf count2.Count)
   threadCounter1.IsBackground = True
   threadCounter1.Start()
   threadCounter2.Start()
End Sub

You'll notice here that you are starting threadCounter1, but cmdBeep_Click now has a call to the Abort method on the Thread class. Abort kills a running thread. To test this, run the application and make sure that you press the Beep button before the Count method is done on threadCounter1. If you click the Beep button before threadCounter1 finishes, it will be killed. You could verify this by waiting and noticing that the message box is never displayed. Or, you could watch the Output window in the VB .NET IDE and you will see when the thread has terminated.

Synchronization

The final topic for threading is that of thread synchronization. In some applications, you might want a thread that retrieves the information, another that sorts the data retrieved, and a third that loads the data into a grid. You might want these on three separate threads because it might be possible to re-sort later without having to fetch the data again. Therefore, you want these three actions to run on three separate threads, but at times they must run in sequence. It wouldn't make sense to run the data retrieval and data sorting threads at the same time. Therefore, you will need to have the sorting thread wait until the retrieval thread is finished.

Events are the common way to notify various parts of the program when certain threads are done. You saw this when you were looking for a way to return data from a thread; you passed that data back as the parameter of the event.

VB .NET provides you with a way of synchronizing shared data: the SyncLock statement. SyncLock allows you to control access to shared resources, much as locks in a database prevent multiple people from accessing the same item at the same time.

If you have a class with shared properties or methods, you can place a SyncLock block inside a shared member to make sure that only one thread accesses that shared member at a time. For example, create the following class in the same file as your Form1 class:

Class SyncTest
   Public Shared Sub SyncMe()
      Dim lCount As Long
      For lCount = 1 To 1000000000
         'do nothing
      Next
   End Sub
End Class

So far, this is nothing special. Now, you'll stretch the purpose of the Beep button. Add the following code to cmdBeep_Click:

Private Sub cmdBeep_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdBeep.Click
   Dim oST1 As New SyncTest()
   Dim oST2 As New SyncTest()
   Dim threadX As New System.Threading.Thread(AddressOf oST1.SyncMe)
   Dim threadY As New System.Threading.Thread(AddressOf oST2.SyncMe)
   threadX.Name = "threadX"
   threadY.Name = "threadY"
   threadX.Start()
   threadY.Start()
End Sub

Here, you are instantiating two instances of the SyncTest object, and then calling the SyncMe method on two different threads. The way to watch this is to monitor the Output window in the IDE. As Figure 11.4 demonstrates, you can see when the threads finish executing, and they are easier to see because you set the Name properties for them. Time how long it takes for both to finish. On the test machine, it took twenty seconds, but both threads finish at almost exactly the same time.

Figure 11.4. The two threads have finished, as noted in the Output window inside the Visual Studio .NET IDE.


Now, modify the code in the SyncTest class to look like this:

Class SyncTest
   Public Shared Sub SyncMe()
      Dim lCount As Long
      SyncLock GetType(SyncTest)
         For lCount = 1 To 1000000000
            'do nothing
         Next
      End SyncLock
   End Sub
End Class

The only difference here is that you have added the SyncLock statement to synchronize access to the shared SyncMe method. This means that you will allow only one thread to access the shared resource at a time, much like locking an update to a record in a relational database. Now, run the project again and time how long it takes the threads to end. The total time should be about the same, but the first thread, threadX, finishes at the halfway mark, and threadY finishes at the end of the time. This is because the threads are now running in sequence rather than parallel, thanks to the SyncLock command.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.127.221