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.  Some of the other project types, such as the Window’s service project, are by default multithreaded apartments.  The MTA style will also be discussed later.  In these situations, WaitAll can be used very effectively. 

    The last method we will examine is WaitAny.  This method waits until any one object in the array is signaled.  An example of its use could be a dictionary search engine.  The program could start two threads, the first that started with the letter A and the second that started with the letter Z.  The first match found by either thread will terminate the others that are searching and return control to the main application.  The return of this method tells you the position of the array that was signaled.  Like the other two methods, you can wait indefinitely or for a specific amount of time. 

Let’s look at a code example. 

Dim WaitAnyEvents(1) As AutoResetEvent

Private Sub Start_Click(ByVal sender As System.Object, ByVal e As System.  EventArgs)

Handles Button1.Click

Dim Thread1 As Thread

Dim Thread2 As Thread

Thread1 = New Thread(AddressOf Thread1Work)

Thread2 = New Thread(AddressOf Thread2Work)

WaitAnyEvents(0) = New AutoResetEvent(False)

WaitAnyEvents(1) = New AutoResetEvent(False)

Thread1.Start()

Thread2.Start()

WaitHandle.WaitAny(WaitAnyEvents)

Console.WriteLine("One thread done exiting main thread")

End Sub

Private Sub Thread1Work()

Thread.Sleep(5000)

Console.WriteLine("Thread1 done")

WaitAnyEvents(0).  Set()

End Sub

Private Sub Thread2Work()

Thread.  Sleep(3000)

Console.WriteLine("Thread2 done")

WaitAnyEvents(1).  Set()

End Sub

    In examining the above code, we see that an array of AutoResetEvent has been created as a form level variable so that all subroutines can access it.  We have put a command button on the form.  This button is the main worker of the example.  When it is clicked, we create two new threads and assign their individual subs to run upon starting.  The subs simulate work by sleeping for a while.  When done sleeping, a string is out put to the debug window and the corresponding AutoResetEvent is signaled.  This causes the main thread to resume running.  You should receive the following output from the example:

Thread2 Done

One thread done exiting main thread

Thread1 done

    The output shows that the main thread resumes running after the first object has been released.  Because the main thread doesn’t abort the first thread, Thread1, it eventually finishes outputting its string “Thread1 done”.  If the other threads are no longer needed they should be aborted manually from your main thread with a call to Abort

    Now let’s examine a way to signal an event and have it stay signaled, the ManualResetEvent.  This event will stay signaled no matter how many threads do a wait method on it.  This only way to change the state is to call Reset.  You can use the object to control access to data that multiple threads are waiting on.  For example, we might have two threads or more, we might not know (or care), waiting on a piece of data that another thread is calculating.  When this thread gets done with its work, we can let all other threads in to access the data.  At some later time if we determine that the data needs to be recalculated, we can turn off the threads from accessing it.  Then do our new calculations. 

Let’s look at some code now. 

Private ManualWaitEvent As ManualResetEvent

Dim Thread1 As Thread

Dim sData As String

Private Sub Form1_Load(ByVal sender As System.Object,

ByVal e As System.  EventArgs) Handles MyBase.Load

ManualWaitEvent = New ManualResetEvent(False)

Thread1 = New Thread(AddressOf ReadWork)

Thread1.IsBackground = True

Thread1.Start()

End Sub

Private Sub ReadWork()

'this method will wait until ManualWaitEvent is

'signaled

Dim i As Integer

For i = 0 To 100

ManualWaitEvent.WaitOne()

Console.WriteLine(sData & i.  ToString())

Thread.Sleep(1000)

Next 'i

End Sub

Private Sub btnSet_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSet.  Click

sData = "Work Done: "

ManualWaitEvent.Set()

End Sub

Private Sub btnReset_Click(ByVal sender As System.Object,

ByVal e As System.EventArgs) Handles btnReset.Click

ManualWaitEvent.Reset()

End Sub

    When the form loads, we create a new instance of a ManualResetEvent in the unsignaled state.  A thread is created and started.  The thread then waits until the event becomes signaled.  When signaled it reads a string that we are using to represent our data.  This is a very powerful method of controlling synchronization when you have multiple threads.  It lets you fine tune access to variables easily.  You can easily switch on and off access to the data. 

    Every second, the thread will output “Work Done: “ and the value of i until the ManualWaitEvent is unsignaled by pressing the reset button.  If the set button is pressed again the thread will resume its work and continue to output data to the output window.  Every time ManualWaitEvent.  WaitOne() is called, a check of the state of ManualWaitEvent is done.  If this call were outside of the loop, all one hundred values of i would have been printed the first time the set button was pressed. 

    Also note the IsBackground call in the form load event.  This makes Thread1 a child thread to the main process thread.  If the main thread is terminated, the operating system will also terminate any background threads related to the main one.  If the thread were not a background thread, it would continue running until it was finished, even when we closed our main thread out.  If the state of ManualWaitEvent were unsignaled, the thread would be waiting on an object that could never be signaled again since our main form was gone.  This results in the process being left in memory.  This should be avoided by making all threads background threads, unless it is 100% necessary for the thread to finish regardless of the state of the application.  Make sure that these non-background threads have access to any resources they need also.  If termination of the main running program disposes of a needed resource, the thread will never finish or result in an error. 

Mutex Class

    The next class in our list, Mutex, can be thought of as a more powerful version of Monitor.  Like AutoResetEvent and ManualResetEvent, it is derived from WaitHandle.  An advantage of Mutex over Monitor is that you can use the methods from WaitHandle such as WaitOne.  A disadvantage is that is much slower, at about half as fast as MonitorMutex is very useful when you must control access to a resource that could be accessed through multiple processes, like a data file used by several applications you have created.  To write to the file, the writing thread must have total access to the file throughout the operating system. 

    When you create a Mutex, you can assign it a name.  If the name exists anywhere in the operating system then that Mutex object instance will be returned.  This is the reason why Mutex is slower, also.  The system must be checked to see if the Mutex already exists.  If it doesn’t exist, a new one is created.  When the last thread in the operating system that references the named Mutex terminates, the Mutex is destroyed.  The following code example shows how to use a Mutex to control access to a file. 

Our first program:

Dim mutexFile As Mutex

Private Sub btnSetMutex_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSetMutex.  Click

mutexFile = New Mutex(False, "Mutex Name")

mutexFile.WaitOne()

'do some file manipulation here such as write to it

'For demonstration purposes we will release

'the mutex in another button click

End Sub

Private Sub btnRelease_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRelease.  Click

mutexFile.ReleaseMutex()

End Sub

Our Second Program:

Private Sub btnAquireMutex_Click(ByVal sender As

System. Object, ByVal e As System. EventArgs) Handles

btnAquireMutex. Click

Dim mutexFile As Mutex

mutexFile = New Mutex(False, "Mutex Name")

mutexFile.WaitOne()‘Wait until the file is open

Console.WriteLine("Mutex was released from another

process")

‘Now I know that I have explicit access to the file

‘I can write to it now. 

mutexFile.ReleaseMutex()

End Sub

    Let’s examine the first program.  A Mutex called mutexFile is created.  Internally to the operating system, we name the mutex “Mutex Name”.  This is the name that will be used to resolve any other calls to the same mutex from any other application that we create.  On a form we have two buttons.  For demonstration purposes, one button will acquire a lock on the resource, in this case the file, using the Mutex and the other button will release the lock.  This simulates a long running process on the file.  As with the other synchronization classes, you should make sure to call RelaseMutex sometime after a lock is acquired or a block on the resource will occur. 

    The second program is very straightforward.  We create a Mutex object called fileMutex making sure we have named it the same as in the first program, “Mutex Name”.  If this is not done the Mutex classes will refer to different mutexes in the operating system.  Then WaitOne is called without a timeout value.  This will make the thread wait until the Mutex has been released.  When the release button is clicked in the first program, the second can continue running since it can now acquire access to the resource.  Mutex was released from another process is printed in the output window.  You can also close the first program and the lock will be released.  When a thread exits that has a Mutex lock on a resource, ReleaseMutex is automatically called for you. 

    In summary, remember that Monitor should be used most of the time.  It is faster than a MutexMutex should only be used when you need to synchronize across multiple processes to gain access to a common resource among several programs that you have written.  Even though Mutex allows for the wait methods where Monitor does not, the other WaitHandle classes should be considered before Mutex if you need the wait methods first. 

ReaderWriterLock Object

    Many times, you read data much more often than you write it.  Traditional synchronization can be overkill in these situations as it would lock resources when threads are reading or writing to the resource.  A more efficient way has been added to the framework to handle this.  The ReaderWriterLock is a synchronization class that allows multiple threads to read a variable, but only one thread to write to it at a time. 

    When acquiring a lock, the write thread must also wait until all reader threads have unlocked the object before obtaining an exclusive write lock.  All readers will then be blocked until the writer thread releases its lock.  The power of the class comes from the fact that it will allow multiple reader locks to access the resource at the same time.  We will look first at how to acquire reader locks on an object. 

Dim lData As Long = 1

Dim objLock As ReaderWriterLock

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

e As System.  EventArgs) Handles btnRun.  Click

Dim Thread1 As Thread

Dim Thread2 As Thread

objLock = New ReaderWriterLock()

Thread1 = New Thread(AddressOf Thread1Work)

Thread2 = New Thread(AddressOf Thread2Work)

Thread1.Start()

Thread2.Start()

End Sub

Private Sub Thread1Work()

Dim i As Integer

For i = 1 To 10

objLock.AcquireReaderLock(1000)

Console.WriteLine(lData & " Thread 1")

Thread.Sleep(10)

objLock.ReleaseReaderLock()

Next

End Sub

Private Sub Thread2Work()

Dim i As Integer

For i = 1 To 10

objLock.AcquireReaderLock(1000)

Console.WriteLine(lData & " Thread 2")

objLock.ReleaseReaderLock()

Next

End Sub

    We create an instance of a ReaderWriterLock object called objLock.  Then two threads are spawned, both of which do a quick loop that writes the value of lData to the console window ten times.  The first thread also has a ten-millisecond sleep call.  This allows us to see that the second thread continues to get a reader lock on objLock even though the first already has one.  Note also that we have passed a millisecond time limit to the methods.  You must pass a timeout value to AcquireReaderLock.  If you wish to wait infinitely, use the constant Timeout.  Infinite

The output should be something similar to the following:

1 Thread 1

1 Thread 2

1 Thread 2

1 Thread 2

1 Thread 2

1 Thread 2

1 Thread 2

1 Thread 2

1 Thread 2

1 Thread 2

1 Thread 2

1 Thread 1

1 Thread 1

1 Thread 1

1 Thread 1

1 Thread 1

1 Thread 1

1 Thread 1

1 Thread 1

1 Thread 1

This shows that the second thread ran while the first had a ReaderLock on the lData integer. 

    If needed, there is also a method IsReaderLockHeld that will return true if the current thread already has a reader lock.  This helps keep track of multiple locks by one thread.  For each call to AcquireReaderLock a subsequent call to ReleaseReaderLock is required.  If you do not call ReleaseReaderLock the same number of times, the reader lock is never fully released, never allowing a write to the resource.  IsReaderLockHeld can be checked to see if a reader lock is already active on the thread, and if so not acquire another one. 

    Now let’s examine how to update the variable.  A writer lock can be obtained by calling AcquireWriterLock.  Once all reader locks have been released, the method will obtain an exclusive lock on the variable.  When updating the variable, all reader threads will be locked out until ReleaseWriterLock is called.  Let’s examine the code for this. 

Dim lData As Long = 1

Dim objLock As ReaderWriterLock

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

e As System.EventArgs) Handles btnRun.  Click

Dim Thread1 As Thread

Dim Thread2 As Thread

Dim Thread3 As Thread

objLock = New ReaderWriterLock()

Thread1 = New Thread(AddressOf Thread1Work)

Thread2 = New Thread(AddressOf Thread2Work)

Thread3 = New Thread(AddressOf Thread3Work)

Thread1.  Start()

Thread2.  Start()

Thread3.  Start()

End Sub

Private Sub Thread1Work()

Dim i As Integer

For i = 1 To 10

objLock.AcquireReaderLock(1000)

Console.WriteLine(lData & " Thread 1")

Thread.Sleep(100)

objLock.ReleaseReaderLock()

Next

End Sub

Private Sub Thread2Work()

Dim i As Integer

For i = 1 To 10

objLock.AcquireReaderLock(1000)

Console.WriteLine(lData & " Thread 2")

Thread.Sleep(100)

objLock.ReleaseReaderLock()

Next

End Sub

Private Sub Thread3Work()

objLock.AcquireWriterLock(Timeout.  Infinite)

lData = 2

Console.WriteLine("Thread 3 updated lData")

objLock.ReleaseWriterLock()

End Sub

    You will notice that we have added a new thread, Thread3 and a function for it to run.  This new function acquires a writer lock on the object and then updates lData to 2.  The first two threads, Thread1 and Thread2, are put to sleep for one hundred milliseconds to allow thread three to start.  When examining the output from this code, you will see that thread three waits until threads one and two release their locks.  This thread three updates the variable.  Thread one and two must then wait on it.  As with the reader lock, there is also a method called IsWriterLockHeld that will return true if the current thread has a writer lock.  You should get output similar to below:

1 Thread 1

1 Thread 2

Thread 3 updated lData

2 Thread 2

2 Thread 1

2 Thread 2

2 Thread 1

2 Thread 2

2 Thread 1

2 Thread 2

2 Thread 1

2 Thread 2

2 Thread 1

2 Thread 2

2 Thread 1

2 Thread 2

2 Thread 1

2 Thread 2

2 Thread 1

2 Thread 2

2 Thread 1

    Another useful method of the ReaderWriterLock class is the UpgradeToWriterLock method.  This method allows a reader lock to become a writer lock to update the data.  Sometimes it is useful to check the value of a data item to see if it should be updated.  Acquiring a writer lock to check the variable is wasted time and processing power.  By getting a reader lock first other reader threads are allowed to continue accessing the variable until you determine an update is needed.  Once the update is needed, UpgradeToWriterLock is called locking the resource for update as soon as it can acquire the lock.  Just like AcquireWriterLock, UpgradeToWriterLock must wait until all readers accessing the resource are done.  Now let’s look at the code. 

Dim lData As Long = 1

Dim objLock As ReaderWriterLock

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

e As System.  EventArgs) Handles btnRun.Click

Dim Thread1 As Thread

Dim Thread2 As Thread

objLock = New ReaderWriterLock()

Thread1 = New Thread(AddressOf Thread1Work)

Thread2 = New Thread(AddressOf Thread2Work)

Thread1.Start()

Thread2.Start()

End Sub

Private Sub Thread1Work()

Dim i As Integer

For i = 1 To 10

objLock.AcquireReaderLock(1000)

If lData = i Then

objLock.UpgradeToWriterLock(Timeout.  Infinite)

lData = i + 1

Console.WriteLine("lData is now " & lData)

End If

Thread.Sleep(20)

objLock.ReleaseReaderLock()

Next

End Sub

Private Sub Thread2Work()

Dim i As Integer

For i = 1 To 10

objLock.AcquireReaderLock(1000)

Console.WriteLine(lData & " Thread 2")

Thread.Sleep(20)

objLock.ReleaseReaderLock()

Next

End Sub

    In this example, we have changed thread one to examine the value of lData after acquiring a reader lock.  If the value of lData is equal to the looping variable of i (which it always is in our example) then it tries to obtain a writer lock by calling UpgradeToWriterLock.  Nothing special is required to release the writer lock once finished with it.  The normal ReleaseReaderLock will release the upgraded writer lock, or calling DowngradeFromWriterLock can be used also which will be discussed next.  The output should be something similar to the following:

lData is now 2

2 Thread 2

lData is now 3

3 Thread 2

lData is now 4

4 Thread 2

lData is now 5

5 Thread 2

lData is now 6

6 Thread 2

lData is now 7

7 Thread 2

lData is now 8

8 Thread 2

lData is now 9

9 Thread 2

lData is now 10

10 Thread 2

lData is now 11

11 Thread 2

    Opposite of UpgradeToWriterLock we can also use DowngradeFromWriterLock.  Like its name suggests the method will make a writer lock turn to a reader lock.  To use the function, you must pass it a LockCookie.  This cookie can be generated from UpgradeToWriterLock.  Because of the LockCookie requirement, you may only use DowngradeFromWriterLock on the same thread that UpgradeToWriterLock is called. 

    One advantage of DowngradeFromWriterLock is that the call returns immediately and will not block the thread at all.  This happens because it can only be called from a thread that has a writer lock on an object.  This means that no other thread can have a lock; hence the method knows that it is the only thread active on the object.  If read access is still required to the resource this method will eliminate the need to reacquire a read lock on the thread.  If read access is not required anymore, simply use ReleaseReaderLock as shown above.  Let’s examine some code now. 

Dim lData As Long = 1

Dim objLock As ReaderWriterLock

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

e As System.EventArgs) Handles btnRun.  Click

Dim Thread1 As Thread

Dim Thread2 As Thread

objLock = New ReaderWriterLock()

Thread1 = New Thread(AddressOf Thread1Work)

Thread2 = New Thread(AddressOf Thread2Work)

Thread1.Start()

Thread2.Start()

End Sub

Private Sub Thread1Work()

Dim i As Integer

Dim objCookie As LockCookie

For i = 1 To 10

objLock.AcquireReaderLock(1000)

If lData = i Then

objCookie = objLock.UpgradeToWriterLock(Timeout.  Infinite)

lData = i + 1

Console.WriteLine("lData is now " & lData)

objLock.DowngradeFromWriterLock(objCookie)

Console.WriteLine("Downgraded lock")

End If

Thread.Sleep(20)

objLock.ReleaseReaderLock()

Next

End Sub

Private Sub Thread2Work()

Dim i As Integer

For i = 1 To 10

objLock.AcquireReaderLock(1000)

Console.WriteLine(lData & " Thread 2")

Thread.Sleep(20)

objLock.ReleaseReaderLock()

Next

End Sub

The only differences in this code from the UpgradeToWriterLock are the lines:

objCookie = objLock.UpgradeToWriterLock(Timeout.  Infinite)

And

objLock.DowngradeFromWriterLock(oCookie)

Console.WriteLine("Downgraded lock")

    Instead of just waiting until the ReleaseReaderLock is called, we explicitly change the writer lock to a reader lock.  The only real difference between downgrading and releasing the lock are with any other waiting writer locks.  If you downgrade and still have waiting writer locks, they must continue to wait until the downgraded lock is released.  You should see output similar to the following:

1 Thread 2

lData is now 2

Downgraded lock

2 Thread 2

lData is now 3

Downgraded lock

3 Thread 2

lData is now 4

Downgraded lock

4 Thread 2

lData is now 5

Downgraded lock

5 Thread 2

lData is now 6

Downgraded lock

6 Thread 2

6 Thread 2

lData is now 7

Downgraded lock

7 Thread 2

lData is now 8

Downgraded lock

8 Thread 2

lData is now 9

Downgraded lock

9 Thread 2

lData is now 10

Downgraded lock

lData is now 11

Downgraded lock

    Two other methods of note on the ReaderWriterLock class are ReleaseLock and RestoreLockReleaseLock immediately drops all locks that the current thread holds.  It returns a LockCookie just like UpgradeToWriterLock that can be used in RestoreLock.  When used, the LockCookie returns the thread back to the exact lock state that it held before.  To handle the fact that other threads could have acquired locks on the object, the method will block until it can resolve all of its previous locks.  The code is as follows:

Dim oLock As ReaderWriterLock

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

e As System.  EventArgs) Handles btnRun.Click

Dim Thread1 As Thread

Dim objCookie As LockCookie

objLock = New ReaderWriterLock()

Thread1 = New Thread(AddressOf Thread1Work)

objLock.AcquireWriterLock(Timeout.  Infinite)

Thread1.Start()

Thread.Sleep(1000)

objCookie = objLock.ReleaseLock

Thread1 = New Thread(AddressOf Thread1Work)

Thread1.Start()

Thread.Sleep(1000)

objLock.RestoreLock(oCookie)

Thread.Sleep(1000)

Thread1 = New Thread(AddressOf Thread1Work)

Thread1.Start()

End Sub

Private Sub Thread1Work()

Try

objLock.AcquireReaderLock(10)

Console.WriteLine("Got a reader lock")

objLock.ReleaseReaderLock()

Catch

Console.WriteLine("Reader lock not held")

End Try

End Sub

    Examining the code, we first see that a writer lock is acquired.  Thread1 is then started to show that it can’t acquire a reader lock on the object.  The main thread then releases the writer lock by calling ReleaseLock and saving its state to objCookie.Thread1 is then restarted acquiring the reader lock.  A call to RestoreLock is called then with the LockCookie passed to it.  When thread one is restarted at that point it cannot acquire its reader lock.  The call to RestoreLock has replaced the writer lock on the object.  The output looks like the following:

Reader lock not held

Got a reader lock

Reader lock not held

    Another interesting pair of functions in the ReaderWriterLock class is the function WriterSeqNum and AnyWritersSince.WriterSeqNum returns the sequence number of the current lock in the internal queue of the ReaderWriterLock class.  This queue keeps the order of the threads that have requested reader or writer locks on an object.  AnyWritersSince will tell if any writer locks have been released since the call to WriterSeqNum.  This is a good method to check if a piece of data has been updated on another thread.  AnyWritersSince could be used in a large, time-consuming report situation.  If no writers have updated the report data then there is no need to recalculate the report.  The following code will show the methods in action. 

Dim objLock As ReaderWriterLock

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

e As System.  EventArgs) Handles btnRun.Click

Dim objCookie As LockCookie

Dim SeqNum As Integer

Dim Thread1 As Thread

objLock = New ReaderWriterLock()

Thread1 = New Thread(AddressOf Thread1Work)

objLock.AcquireWriterLock(Timeout.  Infinite)

SeqNum = objLock.WriterSeqNum

If objLock.AnyWritersSince(SeqNum) = False Then

Console.WriteLine("We see that no writers have

released yet")

End If

objLock.ReleaseWriterLock()

Thread1.Start()

Thread1.Join()

If objLock.AnyWritersSince(SeqNum) = True Then

Console.WriteLine("We see that a writer has released

now")

End If

End Sub

Public Sub Thread1Work()

objLock.AcquireWriterLock(Timeout.  Infinite)

objLock.ReleaseWriterLock()

End Sub

    First a writer lock is acquired on objLock.  The sequence number is saved in SeqNum.  Then a test to AnyWritersSince is made.  Since no other threads have acquired any writer locks and released them, the method returns false.  Next a thread, Thread1, is started and waited on.  This thread simply acquires a writer lock and releases it.  The main thread then checks AnyWritersSince again using the saved off sequence number.  Since another thread has released a writer lock the method return true this time.  The following output is returned. 

We see that no writers have released yet

We see that a writer has released now

Sponsored Links