Article Options
Premium Sponsor
Premium Sponsor

 »  Home  »  .NET Framework  »  Multithreading in VB.NET  »  Synchronization Continued
Multithreading in VB.NET
by John Spano | Published  07/16/2005 | .NET Framework | Rating:
Synchronization Continued

WaitHandle, AutoResetEvent and ManualResetEvent Classes

    We will now examine a MustInherit type class, WaitHandleWaitHandle provides a class definition for three other classes, Mutex, ManualResetEvent and AutoResetEvent, and provides means for your own objects to inherit synchronization functionality.  These objects allow threads to wait until classes derived from WaitHandle are signaled.  The WaitHandle derived classes add functionality over Monitor in that threads can be programmed to wait until multiple classes are signaled.  Of course, along with more power and flexibility comes more work and chance of problems. 

    The two reset event classes can be used in context with Mutex to provide similar functionality to Monitor.  The major difference between Mutex and Monitor is that Mutex can be used across processes.  You can think of the two reset event classes as being switches.  The thread cannot enter a Mutex unless its object is signaled.  We will examine them in detail next. 

    The AutoResetEvent class can be compared to the Monitor.  Pulse method.  Imagine it as a tollbooth.  Each car has to pay to go through, the signal, and then the gate closes behind the car when it passes making the next car in line pay again.  The AutoResetEvent class is like this.  It automatically goes back to unsignaled after being signaled and a thread goes through, just like Monitor.  PulseManualResetEvent can be described as a water hose, once open it lets everything through until you close it yourself. 

    Let’s examine the AutoResetEvent in detail first.  It comes equipped with two methods to control its state, Set and Reset.  Set allows one thread to acquire the lock on the object.  After allowing a thread to pass through, Reset will automatically be called, returning the state to unsignaled. 

    On the first call to Set the runtime will make sure that the state of the object is signaled.  Multiple calls to Set have no effect if the state is already signaled, and it will still allow only one thread to pass.  You do not know the order of threads for each signal either.  If multiple threads are waiting on an object, you are only guaranteed that one will get in per Set when a wait method is called. 

    Reset can be used to change the state of the object back to unsignaled from signaled before a thread calls a wait method on the object.  Reset will return True if it can change the state back to unsignaled or False if it can not.  It has no effect on an unsignaled object.  The code below will show how an AutoResetEvent works. 

Dim WaitEvent As AutoResetEvent

WaitEvent = New AutoResetEvent(False)

Public Sub DoWork()

'do some long processing task simulate by sleeping

Thread.Sleep(5000)

WaitEvent.Set()

End Sub

Public Sub Thread2()

'we want thread 2 to run after thread1 is

'finished.  It will take

'the data computed by thread 1 and do

'something to it

WaitEvent.WaitOne()’Wait until DoWork is done and the

‘WaitEvent is signaled

'wait until thread 1 is done to keep going

End Sub

    In the above code, we make a new instance of an AutoResetEvent.  Our main thread then would call DoWork while a secondary thread would call Thread2.  When the secondary thread reached the WaitOne call, it would enter the WaitSleepJoin state until the main thread calls the Set method after its long processing task allowing Thread2 to continue execution.  When DoWork calls WaitEvent.Set() it signals that it is available for another thread that is waiting to obtain continue running.  Since our Thread2 is waiting, it continues now. 

    To fully understand the AutoResetEvent class, we must also examine the WaitHandle class.  AutoResetEvent is derived from WaitHandle.  It inherits several methods at which we will look at. 

    The first method, WaitOne, we have already seen in action in the above code sample.  Basically, it will wait until the object has become signaled.  WaitOne without any parameters will wait infinitely until the object becomes signaled.  There are also several overrides that allow you to wait for an amount of time, both in milliseconds or a TimeSpan.  If time elapses on these methods, WaitOne will return false indicating that a lock couldn’t be obtained. 

    The timed methods of WaitOne also take a boolean parameter that is worthy of note.  If you pass false to the parameter, nothing different happens from calling the standard no parameter WaitOne except for the timeout.  If true is passed, and WaitOne is called from a COM+ synchronized context, it will force the thread to exit the context before waiting.  This method won’t affect your code unless you use the COM+ methods of synchronization, which we will discuss later. 

    The next method, WaitAll, is very useful when you have a large amount of work to accomplish and want to use multiple threads to accomplish it.  This allows a thread to wait on multiple objects.  Once all objects in the array are signaled the waiting thread is allowed to continue execution. 

    As with the WaitOne method, the no parameter method waits indefinitely while two other methods exist to wait for a specific amount of time.  The method also has the boolean parameter for exiting a synchronized context.  Be careful when waiting infinitely when using WaitAll.  If you don’t signal all instances of the AutoResetEvent correctly as shown below, your waiting thread will never resume. 

Lets take a look at a code example of how to use WaitAll.  First the form’s code:

Dim WaitAllEvents(1) As AutoResetEvent

Private Sub Button1_Click(ByVal sender As System.  Object, ByVal

e As System.  EventArgs) Handles Button1.  Click

Dim thread1 As Thread

Dim thread2 As Thread

‘first we create 2 threads as assign them to subs

thread1 = New Thread(AddressOf Thread1Work)

thread2 = New Thread(AddressOf Thread2Work)

‘Next our 2 AutoRresetEvent instances are created

WaitAllEvents(0) = New AutoResetEvent(False)

WaitAllEvents(1) = New AutoResetEvent(False)

thread1.Start()

thread2.Start()

‘after starting the threads we tell the main thread to

‘wait until all instances of AutoResetEvent have become

‘signaled with a call to Set()

WaitHandle. WaitAll(WaitAllEvents)

Console.  WriteLine("All threads done exiting main thread")

thread2 = Nothing

thread1 = Nothing

End Sub

Private Sub Thread1Work()

Thread.  Sleep(5000)

Console. WriteLine("Thread1 done")

WaitAllEvents(0). Set() ‘I’m done so signal my Event

End Sub

Private Sub Thread2Work()

Thread. Sleep(3000)

Console. WriteLine("Thread2 done")

WaitAllEvents(1).Set()‘I’m done so signal my Event

End Sub

Now some code in a Module. 

<MTATHREAD()>Public Sub Main()

Dim frm As Form1

frm = New Form1()

frm.ShowDialog()

End Sub

The output from the code is:

Thread2 Done

Thread1 Done

All threads done exiting main thread

    As you can see from the output the main thread waits until all objects in its WaitAllEvents array are signaled.  Another item that is worthy to note here is the attribute <MTATHREAD()>.  This signifies that the main thread should run as a multithreaded apartment style thread and not as a single threaded apartment, which is the default.  WaitAll must be called from a thread that is an MTAThread.  If not it will throw a NotSupportedException.  While done as an example above with a simple WinForm, you should not run your main thread that opens Window’s Forms on an MTAThread.  This will cause some problems with some of the controls. 

    The single threaded apartment style thread model guarantees that only one thread is accessing code at one time.  In order for Windows Forms projects to work correctly, they must be run in a single threaded apartment.  This does not mean than worker threads cannot be created and used.  We will go into more detail about Windows Form synchronization later in the case study.