Advanced Synchronization Techniques
Multithreaded applications often use wait handles and monitor objects to synchronize multiple threads. These sections explain how to use the following .NET Framework classes when synchronizing threads: AutoResetEvent Class, Interlocked Class, ManualResetEvent Class, Monitor Class, Mutex Class, ReaderWriterLock Class, Timer Class, WaitHandle Class
Wait handles are objects that signal the status of one thread to another thread. Threads can use wait handles to notify other threads that they need exclusive access to a resource. Other threads must then wait to use this resource until the wait handle is no longer in use. Wait handles have two states, signaled and nonsignaled. A wait handle that is not owned by any thread is in the signaled state. A wait handle that is owned by a thread is in the nonsignaled state.
Threads request ownership of a wait handle by calling one of the wait methods, such as WaitOne, WaitAny, or WaitAll. The wait methods are blocking calls that are similar to the Join method of an individual thread.
- If no other thread owns the wait handle, the call immediately returns True, the wait handle's status is changed to nonsignaled, and the thread that owns the wait handle continues to run.
- If a thread calls one of a wait handle's wait methods, but the wait handle is owned by another thread, the calling thread will either wait for a specified time (if a timeout is specified) or wait indefinitely (if no timeout is specified) for the other thread to release the wait handle. If a timeout is specified and the wait handle is released before the timeout expires, the call returns True. Otherwise, the call to wait returns False, and the calling thread continues to run.
Threads that own a wait handle call the Set method when they are done or when they no longer need the wait handle. Other threads can reset the status of a wait handle to nonsignaled by either calling the Reset method or by calling WaitOne, WaitAll, or WaitAny and successfully waiting for a thread to call Set. AutoResetEvent handles are automatically reset to nonsignaled by the system after a single waiting thread has been released. If no threads are waiting, the event object's state remains signaled.
There are three kinds of wait handles commonly used with Visual Basic .NET: mutex objects, ManualResetEvent, AutoResetEvent. The last two are often referred to as synchronization events.
Mutex objects are synchronization objects that can only be owned by a single thread at a time. In fact, the name, mutex, is derived from the fact that ownership of mutex objects is mutually exclusive. Threads request ownership of the mutex object when they require exclusive access to a resource. Because only one thread can own a mutex object at any time, other threads must wait for ownership of a mutex object before using the resource.
The WaitOne method causes a calling thread to wait for ownership of a mutex object. If a thread terminates normally while owning a mutex object, the state of the mutex object is set to signaled and the next waiting thread gets ownership.
Synchronization events are used to notify other threads that something has occurred or that a resource is available. Do not be fooled by the fact that these items use the word event. Synchronization events, which are unlike other Visual Basic events, are really wait handles. Like other wait handles, synchronization events have two states, signaled and nonsignaled. Threads that call one of the wait methods of a synchronization event must wait until another thread signals the event by calling the Set method. There are two synchronization event classes. Threads set the status of ManualResetEvent instances to signaled using the Set method. Threads set the status of ManualResetEvent instances to nonsignaled using the Reset method or when control returns to a waiting WaitOne call. Instances of the AutoResetEvent class can also be set to signaled using Set, but they automatically return to nonsignaled as soon as a waiting thread is notified that the event became signaled.
Monitor Objects and SyncLock
Monitor objects are used to ensure that a block of code runs without being interrupted by code running on other threads. In other words, code in other threads cannot run until code in the synchronized code block has finished.
Suppose, for example, that you have a program that repeatedly and asynchronously reads data and displays the results. With operating systems that use preemptive multitasking, a running thread can be interrupted by the operating system to allow time for some other thread to run. Without synchronization, it is possible that you could get a partially updated view of the data if the object that represents the data is modified by another thread while the data is being displayed. Monitor objects guarantee that a section of code will run without being interrupted. Visual Basic .NET provides the SyncLock and End SyncLock statements to simplify access to monitor objects. Visual C#® .NET uses the Lock keyword in the same way.
Note Access to an object is locked out only if the accessing code is contained within a SyncLock block on the same object instance.
For information on the SyncLock statement, see SyncLock Statement
You can use the methods of the Interlocked class to prevent problems that can occur when multiple threads attempt to simultaneously update or compare the same value. The methods of this class let you safely increment, decrement, exchange, and compare values from any thread. The following example shows how to use the Increment method to increment a variable that is shared by procedures running on separate threads.
Sub ThreadA(ByRef IntA As Integer) System.Threading.Interlocked.Increment(IntA) End Sub Sub ThreadB(ByRef IntA As Integer) System.Threading.Interlocked.Increment(IntA) End Sub
For information on the Interlocked Class, see Interlocked Class
In some cases, you may want to lock a resource only when data is being written and permit multiple clients to simultaneously read data when data is not being updated. The ReaderWriterLock class enforces exclusive access to a resource while a thread is modifying the resource, but it allows non-exclusive access when reading the resource. ReaderWriter locks are a useful alternative to exclusive locks that cause other threads to wait, even when those threads do not need to update data.
For information on the ReaderWriterLock Class, see ReaderWriterLock Class
Thread synchronization is invaluable in multithreaded applications, but there is always the danger of deadlocks where multiple threads wait for each other. Analogous to when cars are stopped at a four-way stop and each person is waiting for another to go, deadlocks bring everything to a halt. Needless to say, avoiding deadlocks is important. There are many ways to get into deadlock situations but careful planning is key to avoiding them. You can often predict deadlock situations by diagramming multithreaded applications before you start coding.
System.Threading Namespace | Thread States | Multithreaded Applications | Parameters and Return Values for Multithreaded Procedures | Multithreading with Forms and Controls | SyncLock Statement | Delegates and the AddressOf Operator | Multithreading in Components | WaitOne Method | WaitAll Method | WaitAny Method