lo there being first。 The serial behavior is easy to understand for humans。 Things bee pli cated when you need to think of multiple tasks at the same time。 While the puter has no problem with threads; a human who is thinking in a serial manner codes the logic; and thus the logic could be wrong。 Writing a good threading application should remind you of herding cats or dogs。 If you are not careful with your threads and synchronization; then it is like herding ten cats into a single corner—a nearly impossible task; since cats do not respond well to mands。 But if you are careful and conscientious; then it is like herding ten dogs into a corner—fairly easy if the dogs are trained。 Waiting for the Thread to End Calling Start() will start a thread; causing a task to be executed。 The caller of the thread does not wait for the created thread to end; because the created thread is independent of the caller。 So if you were running a batch process; you would need to wait until all threads have pleted。 The caller logic only needs to start the threads; and thus its work requires a fraction of the time that the threads require。 There is a way for the caller thread to know when a created thread has exited。 This technique involves using the Join() method; like this: Sub ThreadWaitingTask() Console。WriteLine(〃hello there〃) Thread。Sleep(2000) End Sub …………………………………………………………Page 369…………………………………………………………… C HA P TE R 1 3 ■ L E AR N IN G AB O U T M U L T IT HR E AD IN G 347 Private Sub ThreadWaiting() Dim thread As New Thread(AddressOf ThreadWaitingTask) thread。Start() thread。Join() End Sub The line at the end of the code calls the Join() method; which means that the thread calling Join() is blocked until the thread referenced by the instance ends。 A Thread。Sleep() call is used to put the other thread to sleep for the time specified by the parameter—2000 milliseconds; or 2 seconds; in this example。 This code solves the problem of the premature exit of the calling thread; but if the calling thread is going to wait until the created thread exits; what’s the benefit? In this simple example; using Join() adds no benefit。 However; when the caller thread executes many threads; the caller wants to continue only when all threads have finished executing。 So in a multithreading situation; you would want to call Join() on each and every thread。 Another variation of Join() is where a parameter specifies a timeout。 Imagine starting a thread; and in the worst…case scenario; you predict a processing time of 5 minutes。 If the processing time is exceeded; the logic is to forcibly exit the thread。 Here’s the code to implement that logic: If Not thread。Join(300000) Then thread。Abort() End If In the example; calling Join() will cause the executing thread to wait 300;000 milliseconds (5 minutes) before continuing。 If the timeout occurred; the Join() method would return False; and the code would forcibly exit the thread using the Abort() method。 ■Note It is wise to avoid using Abort() except in the most dire of situations; because data will be corrupted。 Creating a Thread with State In the threading example; the threads did not manage any state。 In most cases; your threads will reference some state。 Implementing a ThreadStart Delegate One way to run a thread with state is to define a type that implements a delegate of the type ThreadStart。 The following example defines a class with a method that will be called by a thread。 The technique used is where a delegate from another class instance is passed to the Thread type。 Class ThreadedTask Private whatToSay As String Public Sub New(ByVal whattosay As String) _whatToSay = whattosay End Sub …………………………………………………………Page 370…………………………………………………………… 348 CH AP T E R 1 3 ■ L E A R N I N G A B OU T M U L T I TH R E A DI N G Public Sub MethodToRun() Console。WriteLine(〃I am babbling (〃 & _whatToSay & 〃)〃) End Sub End Class To use the method; the threading code is changed as follows: Dim task As ThreadedTask = New ThreadedTask(〃hello〃) Dim thread As Thread = _ New Thread(AddressOf task。MethodToRun) thread。Start() In the example; the ThreadedTask type is instantiated with a state; and then Thread is instantiated with the stateful task。MethodToRun() method。 When the thread starts; the data member _whatToSay will have some associated state。 The code is logical and has no surprises。 But what if you were to use the stateful method twice; like this: Dim task As ThreadedTask = New ThreadedTask(〃hello〃) Dim thread1 As Thread = New Thread(AddressOf task。MethodToRun) Dim thread2 As Thread = New Thread(AddressOf task。MethodToRun) thread1。Start() thread2。Start() Here; there are two Thread instances; but a single task instance。 There will be two threads doing the same thing; and even worse; two threads sharing the same state。 It is not wrong to share state; but sharing state requires special treatment to ensure that state remains consistent。 You need to instantiate a single ThreadedTask and associate it with a single Thread instance; like this: Dim task1 As Thread