Professional Documents
Culture Documents
NET
Introduction
System Requirements
I will assume that you already have knowledge of basic threading in Visual
Basic.Net. You should know how to create threads and know how to do basic threading
operations like Join and Sleep. A copy of Visual Studio.Net is required to run the code
samples and see the output. The code was written with Visual Studio.Net using version
1.0 of the .Net Framework with service pack 2.
This case study has three main parts. Multithreading requires a technique
called synchronization to eliminate the problems described above so we will first take a
brief look at what synchronization is. Then an in-depth look at all methods available in
Visual Basic.Net for synchronization will be presented where you will learn how to
correctly synchronize a multithreaded application. After this, a look at Window’s Form
synchronization and threading apartment styles will show the differences a programmer
must handle between standard synchronization and visual GUI synchronization.
Synchronization
Dim X as Integer
X=1
X=X+1
Imagine the above situation with multiple threads trying to access the variable
X at the same time. Synchronization is the process of eliminating these kinds of errors.
Without synchronization programming, the computer could stop the first thread at any
point in time, and let the second access the variable. If the second thread was
incrementing X by 1 also, it might finish, and then the computer resumes the original
thread that was running. This thread would restore its variable information, replacing the
new X with the old value, nullifying the work that the second thread accomplished. This
is called a race condition. These errors are very hard to find. It is best to put time in
preventing them. .
To synchronize code, you utilize locks. A lock is a way to tell the computer
that the following group of code should be executed together as a single operation, and
not let other threads have access to the resource that is locked until the locking code is
finished. In the case study, we will examine the different types of locks and objects that
allow locking, and discuss when to use each method. When your code can handle
multiple threads, safely, it is considered thread safe. This common term is used on code
libraries and controls to designate that they are compatible with multiple threads.
Synchronization also adds a new type of bug you have to watch out for,
deadlocking. Deadlocking can occur if you aren’t careful with your locking techniques.
For example, assume that we have two resources, A and B. Thread 1 calls and locks
resource A at the same time thread 2 calls and locks resource B. Thread 1 then requests
resource B and thread 2 requests resource A. This is called a deadlock. Thread 1 can’t
release resource A until it gets resource B, and thread 2 can’t release resource B until it
gets A. Nothing happens and your system can’t ever complete either of the two threads.
Needless to say this is very bad.
The only way to avoid deadlocks is to never allow a situation that could create
one. Code both threads to allocate resources in the same order. Have thread 1 allocate A
and then B, and the same with thread 2. This way thread 2 will never start until thread 1
is finished with resource A. Then it will wait until thread 1 is finished with resource B
before continuing, and avoid the deadlock. Another good practice is to lock resources as
late as possible. Try to avoid getting locks until you absolutely need them and then
release them as soon as possible. Next we shall take a look at all the different methods of
thread synchronization that the common language runtime provides.
Interlocked Class
Dim X as Integer
X=1
X = Interlocked.Increment(X)
X = Interlocked.Decrement(X)
The above code ensures that the computer will not interrupt the increment or
decrement of the variable X.
There are two additional methods in the Interlocked class, Exchange and
CompareExchange. Let’s take a closer look at the two. The Exchange method replaces
the value of a variable with the value supplied. The second value could be a hard coded
value or a variable. Don’t let the name of the method, Exchange, confuse you though.
Only the first variable passed in the first parameter will be replaced by the second. The
method won’t really exchange the values of two variables.
Dim X as Integer = 5
Dim Y as Integer = 1
Dim i As Integer
i = 200
The above code creates a new integer and then assigns the value 200 to it. We
then call the Interlocked.CompareExchange. The method compares the variable i with
200 and since they are the same, it will replace i with DateTime. Now. Day, the current
day of the month.
The SyncLock keyword (lock in C#) gives an easy way to quickly lock parts of
code. It is a built in keyword in Visual Basic. Net now. Take a look at the following
code segment:
SyncLock objLock
sText = “Hello”
End SyncLock
SyncLock Me
sText= “Hello”
End SyncLock
End Sub
Locking the entire object is usually a great waste of time and processing
power. Other methods in the Me object that have locking code based on the Me object
won’t be accessible to any threads while in the lock. If a more flexible approach is
needed, a locking variable can be used. Locks can also only be obtained on reference
types. If a lock on a value type is needed, you must use a locking object as shown
below. The code locks access to iData via a reference type, System. Object. Imagine the
locking object as a key to the code. Only one thread at a time can have the key. This
allows for much greater control over what gets locked. This method will also not lock
the whole Me object. Other threads are free to access other methods of Me, which is
much more efficient and will reduce the possibility of deadlocks.
SyncLock objLock
iData = 3
End SyncLock
End Sub
One drawback to using SyncLock is that other threads must wait forever for the
lock to be released if they need the locked resource. They will never time out. If you
aren’t careful and enter an infinite loop in the locking thread, or hog resources, you can
easily create deadlocks or periods of time where nothing happens. In later sections, better
methods of synchronization will be discussed.
Flow control statements such as GoTo cannot move the code flow into a
SyncLock block of code. The thread must execute the SyncLock keyword. Old Visual
Basic 6 error handling cannot be used from inside a SyncLock block either since it uses
exception handling internally. Since all new code should be written with exception
handling, you probably won’t run into a situation like this unless upgrading a legacy
application. I would highly recommend rewriting any legacy error handling even if the
methods aren’t used for multithreading. Neither of the following code blocks will
compile:
SyncLock Me
On Error GoTo Errhandle ‘won’t compile
Dim i As Integer
i=5
End SyncLock
Exit Sub
Errhandle:
Or:
SyncLock Me
EnterHere:
Dim i As Integer
i=5
End SyncLock
Monitor Class
The Enter function of the Monitor class works just like the SyncLock keyword
and the Exit function is like the End Synclock keywords. Internally SyncLock uses the
Monitor class to implement its functionality and generates the inner Try Finally block of
the code sample for you. Let’s look at the code now:
Public Sub Foo()
Try
Monitor.Enter(objLock)
Try
sText = “Hello”
Finally
Monitor.Exit(objLock)
End Try
Catch e as Exception
MessageBox.Show(e.Message)
End Try
End Sub
This provides the exact same functionality that the SyncLock example above
did. You will also notice that the Exit is contained in the finally clause of a Try Catch
Finally block. This is to ensure that Exit gets called so the thread won’t get locked
infinitely. Monitor. Enter is also called outside of the Try Catch Finally block. This is
so Monitor. Exit won’t get called if the Enter method doesn’t, as it will throw another
exception. So why should we use Monitor, as the SyncLock keyword provides the same
functionality without the extra work of Monitor. We will examine the reasons why
Monitor should be used as we look at the other methods of Monitor.
We said earlier that the SyncLock block would wait indefinitely on the
executing thread to release the lock. The Monitor class provides a much better method to
handle this, the TryEnter method. This is the first reason why you would use Monitor
over SyncLock. This method will allow the calling thread to wait a specific amount of
time to acquire a lock before returning false and stopping its execution. This allows
graceful handling of long running threads or deadlocks. If a deadlock has occurred, you
certainly do not want to add more threads that are trying to get to the deadlocked
resource.
sText = "Hello"
Monitor.Exit(objLock)
End If
End Sub
This example will try to acquire a lock for five seconds. If successful the
stringis set to “Hello”.
Monitor.Enter(objLock)
bPulsed = Monitor.Wait(objLock)
If bPulsed Then
End If
Monitor.Exit(objLock)
The thread is automatically unlocked with the Wait call. You must be sure to
call Monitor. Exit when the thread is pulsed and done with its work, or you will have a
block that could result in a deadlock. The first thread will wait until the pulsing thread
has released its lock. This will make the thread wait until a Monitor. Exit is called, like
the following.
Monitor.Enter(objLock)
Monitor.Pulse(objLock)
Monitor.Exit(objLock)
If the Exit call is left off a block occurs because the waiting thread cannot
obtain its lock on the object that the pulsing thread has. You must also use the same
object to lock on, and pulse from the second thread that the waiting thread used to wait
on, objLock. Also, both Wait and Pulse must be called from a locked block of code,
hence the Enter and Exit calls in the above code. You should exit immediately after
calling Pulse to allow the first thread to perform its work, since the pulsing code has the
current lock on objLock.
The Monitor class also comes equipped with a PulseAll method. Unlike Pulse,
which will only start the next waiting thread, PulseAll removes the wait state from all
waiting threads and allows them to continue processing. As with the Pulse method,
PulseAll must be called from a locked block of code, and on the same object that the
original threads are waiting on.
The Monitor class will provide for most of your threading synchronization
needs. It should be used unless a more specific task calls for the next few classes we will
examine. Here is a review of some good practices to follow when using Monitor:
Exit MUST be called the same number of times Enter is called, or a block will
occur.
Make sure that the object used to call Enter is the same object that is used to call
Exit or the lock will not be released.
Don’t call Exit before calling Enter, or call Exit more times than calling Enter or
an exception will occur.
Place the Exit method call in a Finally block. All code that you wish to lock
should be in the Try section of the corresponding Finally block. The Enter call should be
in its own Try block. This eliminates calling Exit if the Enter fails.
Don’t call Enter on an object that has been set to Nothing or an exception will
occur.
Don’t change the object that you use as the locking object, which brings in 7,
Use a separate locking object, and not the changing object. If you use an object
that has changed, an exception will be generated.
MethodImplAttribute
Code attributes in the Dot Net Framework can sometimes make programming
easier. The MethodImplAttribute is one example of the hundreds of different attributes
that you can use. It is in the System. Runtime. CompilerServices namespace. This
attribute is particularly interesting to synchronization because it can synchronize an entire
method with one simple command.
If you place the attribute before a function and supply the MethodImplOptions.
Synchronized enumeration in the constructor, the entire method will be synchronized
when called. The compiler will create output that wraps the whole function,
MySyncMethod, in a Monitor. Enter and Monitor. Exit block. When a thread starts to
enter the method it will acquire a lock. Upon exiting the method, it will release the lock.
Here is an example of using the attribute.
<METHODIMPLATTRIBUTE(METHODIMPLOPTIONS.SYNCHRONIZED)
>Private
Sub MySyncMethod()
End Sub
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.
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.
Thread.Sleep(5000)
WaitEvent.Set()
End Sub
'something to it
‘WaitEvent is signaled
End Sub
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:
thread1.Start()
thread2.Start()
thread2 = Nothing
thread1 = Nothing
End Sub
Thread. Sleep(5000)
End Sub
Thread. Sleep(3000)
End Sub
frm.ShowDialog()
End Sub
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.
Handles Button1.Click
Thread1.Start()
Thread2.Start()
WaitHandle.WaitAny(WaitAnyEvents)
End Sub
Thread.Sleep(5000)
Console.WriteLine("Thread1 done")
WaitAnyEvents(0). Set()
End Sub
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
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.
Thread1.IsBackground = True
Thread1.Start()
End Sub
'signaled
Dim i As Integer
For i = 0 To 100
ManualWaitEvent.WaitOne()
Thread.Sleep(1000)
Next 'i
End Sub
ManualWaitEvent.Set()
End Sub
ManualWaitEvent.Reset()
End Sub
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 Monitor. Mutex 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.
mutexFile.WaitOne()
End Sub
mutexFile.ReleaseMutex()
End Sub
btnAquireMutex. Click
process")
mutexFile.ReleaseMutex()
End Sub
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.
Thread1.Start()
Thread2.Start()
End Sub
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Thread.Sleep(10)
objLock.ReleaseReaderLock()
Next
End Sub
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
objLock.ReleaseReaderLock()
Next
End Sub
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.
Thread1. Start()
Thread2. Start()
Thread3. Start()
End Sub
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
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Thread.Sleep(100)
objLock.ReleaseReaderLock()
Next
End Sub
objLock.AcquireWriterLock(Timeout. Infinite)
lData = 2
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
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.
Thread1.Start()
Thread2.Start()
End Sub
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
If lData = i Then
objLock.UpgradeToWriterLock(Timeout. Infinite)
lData = i + 1
End If
Thread.Sleep(20)
objLock.ReleaseReaderLock()
Next
End Sub
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
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
Thread1.Start()
Thread2.Start()
End Sub
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
If lData = i Then
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
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Thread.Sleep(20)
objLock.ReleaseReaderLock()
Next
End Sub
The only differences in this code from the UpgradeToWriterLock are the lines:
And
objLock.DowngradeFromWriterLock(oCookie)
Console.WriteLine("Downgraded lock")
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
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.Start()
End Sub
Try
objLock.AcquireReaderLock(10)
objLock.ReleaseReaderLock()
Catch
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:
objLock.AcquireWriterLock(Timeout. Infinite)
SeqNum = objLock.WriterSeqNum
released yet")
End If
objLock.ReleaseWriterLock()
Thread1.Start()
Thread1.Join()
now")
End If
End Sub
objLock.AcquireWriterLock(Timeout. Infinite)
objLock.ReleaseWriterLock()
End Sub
COM+ Synchronization
The dot net framework provides many enterprise services that can be used to
build enterprise applications, one of which is the COM+ method of synchronization.
COM+ offers developers many helpful techniques such as transaction handling between
objects, loosely coupled events, object pooling and synchronization, which we will
discuss here, to name a few. This synchronization method allows the usage of a concept
called a context to provide ways to lock code for synchronization. This method can be
implemented on any class that is derived from ContextBoundObject, or from any class
that derives from ContextBoundObject.
When you use the attribute, COM+ will create a proxy for you that will run all
instances of your object in its context. COM+ will marshal all calls across this proxy
where a performance penalty occurs. The service guarantees that only one thread is
available to run each object at a time.
Earlier the timed methods of the WaitHandle classes were discussed. Recall
that the second parameter of the method was a boolean method that determined whether
to release the synchronized context along with the object lock. If your classes use COM+
synchronization True should be passed for this parameter or deadlocks are risked. True
tells COM+ to exit its synchronized context before the runtime allows the thread to wait.
This allows other threads to then get access to the context avoiding deadlocks. If you
don’t exit the context, the . Net runtime will allow other threads access to the locked
object since an exit method has been called. When the next thread acquires a lock on the
locking object it will then try to enter the context, which is still locked resulting in a
deadlock.
Now that we have examined all the methods that Visual Basic offers for
synchronization, we will take a look at Window’s Form projects and what apartment
threading is. The most common types of threading on the Windows platform are single
threaded apartments (STA) or multithreaded apartments (MTA). Window’s forms must
be hosted in an STA apartment because some Window’s Form controls are based on
standard Windows COM controls that require an STA environment. Background threads
can still be utilized to update forms, but synchronization must be done differently. As we
examine the two apartment styles, we will look at how to do correct synchronization with
Window’s Forms.
By default all Windows’ Form projects in Visual Basic are STA. Visual Basic
applies the <STATHREAD()>attribute to the main entry point in the application for you
behind the scenes. While you could override this attribute and change it to an MTA
apartment, you should not or problems will occur with the COM controls as discussed
above.
MTA, sometimes called free threading, is much harder to program than STA.
This is another reason why we encounter STA COM components most of the time. MTA
means that more than one thread can access an object at any given point in time safely.
When programming for MTA, you must be sure to include good synchronization and
design as discussed in the case study. Any number of threads could be accessing objects
in your library at any time.
The type of threading model that the current thread is using can be determined
simply with the following code.
MessageBox.Show(sThreadType)
sThreadType will equal “STA” or “MTA” after the call. There is also an
ApartmentState object that can be set to Thread.CurrentThread.AppartmentState().
Apt = Thread.CurrentThread.ApartmentState()
MessageBox.Show(apt.ToString())
Window’s Form classes provide built in methods to update GUI elements from
other threads. These methods should be used exclusively. The methods are called
Invoke, BeginInvoke, EndInvoke and CreateGraphics. All of the methods can be called
from any thread. When called, the methods provide a way to work with the control from
the main Window’s Form thread. Let’s see how we can use the methods.
Thread1.Start()
End Sub
txtList.Invoke(del)
Console.WriteLine("Thread 1 Done")
End Sub
Dim i As Integer
For i = 0 To 100
i.ToString() + vbCrLf
Next 'i
Console.WriteLine("Delegate Done")
End Sub
To call Invoke, a delegate sub is created. This sub simply adds a new line to
the textbox with the words “A New Line”. When our new thread is started a new
instance of the delegate is created. The new delegate is then passed to txtList. Invoke
updating the text.
The Invoke method runs any code in the delegate synchronously on the thread.
The output from the run will show this:
Delegate Done
Thread 1 Done
Thread1.Start()
End Sub
Result = txtList.BeginInvoke(del)
Console.WriteLine("Thread 1 Done")
Console.WriteLine(Result.IsCompleted.ToString())
txtList.EndInvoke(Result)
Console.WriteLine(Result.IsCompleted.ToString())
End Sub
Dim i As Integer
For i = 0 To 100
i.ToString() + vbCrLf
Next 'i
Console.WriteLine("Delegate Done")
End Sub
Output:
Thread 1 Done
False
Delegate Done
True
As we see from the output, Thread 1 completed before the delegate finished.
Then the first call to Result.IsCompleted returns false, signifying that the delegate is still
running. Thread 1 is then put to sleep with the EndInvoke call allowing the delegate time
to finish. The next call to Result.IsCompleted returns true.
The code also shows two methods of getting the status of an asynchronous
call. The first method was the line Result = txtList.BeginInvoke(del). The Result
variable will contain the current results of the asynchronous call. The other method is
with the EndInvoke call, which we said earlier, would block until the asynchronous call is
finished. The last output of true shows that this behavior happened.
When using graphics drawing methods with Window’s Forms you must be sure
to do all work on the main thread also. The CreateGraphics method makes sure of this
for you. It can be called from other threads safely like the invoke methods. The
Graphics object returned will run all calls in the correct thread for you. The Graphics
object is considered thread safe so no additional locking objects are necessary.
In your reading or study of .Net code, the volatile C# keyword might come up.
This keyword does not exist in Visual Basic. Don’t worry though; it doesn’t add any
functionality to C# that can’t be done with the other synchronization objects discussed in
this case study.
The volatile keyword tells the compiler that the variable it references could
change at anytime and that no optimizations should be done to it. It will prohibit the
compiler from storing the variable in a register and force it read it new from memory
each time.
Variables marked as volatile aren’t necessarily thread safe. They only insure
that each read of the variable is the latest information. To see what a declaration looks
like look at the following code snip-it, which declares an Integer variable as volatile.
Summary