As you write a threaded application, you may need to synchronize individual threads with other parts of your program. Synchronization provides a compromise between the unstructured nature of multithreaded programming and the structured order of synchronous processing.
You use synchronization techniques:
- To explicitly control the order in which code runs whenever tasks must be performed in a specific sequence
- To prevent the problems that can occur when two threads share the same resource at the same time.
For example, you could use synchronization to cause a display procedure to wait until a data retrieval procedure that is running on another thread is complete.
Using events is a common way to synchronize threaded applications. In this scenario, each thread raises an event to signal its status to the main program or other threads. Other parts of the application, outside the thread that raised the event, handle the event and process the information.
Note When an event is raised from one object to another object, the event handler will execute on the same thread that the RaiseEvent occurred. Therefore, if the event handler accesses shared data it must itself use synchronization if that data may be simultaneously accessed on a different thread
Although using events is a simple way to synchronize threads, there are limitations to this approach. Visual Basic .NET includes other features for controlling individual threads and their access to specific sections of code. The following sections discuss synchronization techniques.
There are two approaches to synchronization, polling and using synchronization objects. Polling repeatedly checks the status of an asynchronous call from within a loop. Polling is the least efficient way to manage threads because it wastes resources by repeatedly checking the status of the various thread properties.
For example, the IsAlive property can be used when polling to see if a thread has exited. Use this property with caution because a thread that is alive is not necessarily running. You can use the thread's ThreadState property to get more detailed information about a thread's status. Because threads can be in more than one state at any given time, the value stored in ThreadState can be a combination of the values in the System.Threading.Threadstate enumeration. Consequently, you should carefully check all relevant thread states when polling. For example, if a thread's state indicates that it is not Running, it may be done. On the other hand, it may be suspended or sleeping.
Waiting for a Thread to Finish
The Thread.Join method is useful for determining if a thread has completed before starting another task. The Join method waits a specified amount of time for a thread to end. If the thread ends before the timeout, Join returns True; otherwise it returns False. For information on Join, see Thread.Join Method
As you can imagine, polling sacrifices many of the advantages of multithreading in return for control over the order that threads run. Because it is so inefficient, polling generally not recommended. A more efficient approach would use the Join method to control threads. Join causes a calling procedure to wait either until a thread is done or until the call times out if a timeout is specified. The name, join, is based on the idea that creating a new thread is a fork in the execution path. You use Join to merge separate execution paths into a single thread again.
Figure 1 Threading
One point should be clear: Join is a synchronous or blocking call. Once you call Join or a wait method of a wait handle, the calling procedure stops and waits for the thread to signal that it is done.
Sub JoinThreads() Dim Thread1 As New System.Threading.Thread(AddressOf SomeTask) Thread1.Start() Thread1.Join() ' Wait for the thread to finish. MsgBox("Thread is done") End Sub
These simple ways of controlling threads, which are useful when you are managing a small number of threads, are difficult to use with large projects. The next section discusses some advanced techniques you can use to synchronize threads.