Thus far all the examples could have been accomplished by creating an instance or instances of the Thread class and managing the thread directly. It is easiest to implement asynchronous behavior using the Timer control and the Application.Idle event, and easier to use threads in the thread pool. Using Thread objects is not significantly more difficult, but you do have to actively manage the threaded behavior and the thread objects. When you're writing code for money, the more difficult something is, the more it will cost. Unfortunately, that extra cost is seldom linear.
Creating and Using Threads
The basic steps for using the Thread class are an expansion of using the thread pool. The mechanics of using the Thread class include creating an instance of the Thread class, passing a worker method in the form of a delegate to the Thread constructor, setting Thread.IsBackground = True, and cleaning up when the thread is finished. In between you will have to contrive ways to synchronize the thread's interaction with shared resources; you can use several of the ways already discussed, including the SyncLock..End SyncLock construct, WaitHandle objects, and the Monitor class.
Table 14.1 lists some of the basic members of the Thread class that are available to facilitate thread management.
Table 14.1. Members of the Thread Class
You already have some experience with the Dice demo. An updated copy of the Dice demo is in ThreadRollDice2 for you to compare to the first version that uses ThreadPool. A much shorter demonstration can be completed using the Monitor example from the section "Synchronization with the Monitor Class" and still demonstrate the salient differences between threading with ThreadPool and with the Thread class.
The revised complete application is defined on this book's Web site in the MonitorDemo2 folder. Listing 14.6 only contains the revised shared Sub Main from Listing 14.5 because that is pretty much all we have to change to use Thread objects.
Listing 14.6 Using instances of the Thread class
1: Public Shared Sub Main() 2: 3: Dim Demo As New MonitorDemo() 4: Demo.FillArray() 5: Dim SortThread As New Thread(AddressOf Demo.SortArray) 6: SortThread.IsBackground = True 7: 8: Dim PrintThread As New Thread(AddressOf Demo.PrintArray) 9: PrintThread.IsBackground = True 10: 11: SortThread.Start() 12: PrintThread.Start() 13: 14: SortThread.Join() 15: PrintThread.Join() 16: 17: Console.ReadLine() 18: 19: End Sub
As you recall from the earlier listing, the complete sample is a console application that uses the Monitor class to swap between two threads that sort and print an array. After each complete set of inner loop comparisons, the sorted i th element is printed on the second thread. In the original listing, ThreadPool was used. Here we use Thread objects that we create.
Lines 56 and 89 each create a Thread object and set its IsBackground property to True. If you forget to set IsBackground to True and forget to stop the thread, your application will mysteriously seem to hang after you exit, while any running foreground threads unwind. Notice that just as with ThreadPool, we are assigning work to the thread in the form of a delegate. The delegate used to construct a Thread object is a ThreadStart delegate, which is essentially the address of a subroutine that takes no arguments. (Recall that the ThreadPool.QueueUserWorkItem method takes a WaitCallBack delegate that requires a single Object argument.)
The two threads are started on lines 11 and 12, and each thread is blocked in turn until each exits. The Console.ReadLine call is provided to suspend closing the console until you have had a chance to review the results.