By their very nature, threads execute independently. Once a thread is started, it will proceed without regard for what any other thread is doing by default. Sometimes this works, but often you need the ability for one thread to let another one know how things are progressing. This article, one in a series on Visual Basic .NET and threading, introduces the techniques used in this multiprocessing technique. The first article contains a list of all of the articles in this series.
-> Threading: Making your program appear to do lots of things at the same time.(Threading)
The previous article, Threading - Locks: InterLock and SyncLock showed how to ensure that one thread would execute and that others would be prevented from executing using SyncLock with this syntax.
SyncLock expression
...[ block ]
End SyncLock
The Monitor object provides even more powerful, but still similar capability. (See that article for more details.) But as noted in earlier articles, deadlocks are hard to prevent with these tools. A deadlock occurs when Thread A needs a resource locked by Thread B and Thread B needs a resource locked by Thread A. When that happens, the whole process just stops. To deal with this kind of problem, you need some way for the threads to communicate.
Just like two people who might have trouble communicating, two threads communicate using a "third party". In the case of threads, one type of third party is called a synchronization event. There are two to choose from: AutoResetEvent and ManualResetEvent. The difference is that AutoResetEvent changes from signaled to unsignaled automatically when it activates a thread. That difference will be more clear when ManualResetEvent is described.
AutoResetEvent
One of the main confusing things about synchronization events is that you can't query the object directly. Even though the code below uses the constant "False" to initialize the event, it's not a boolean. You can only use the methods of the event to control behavior. So the code below writes to the console whenever something happens to make that clear. Here's the code:
Dim theEvent As AutoResetEvent
Sub DoWork()
Console.WriteLine("-------")
Console.WriteLine("Worker Thread Started")
Console.WriteLine("theEvent.WaitOne Executed Next")
theEvent.WaitOne()
Console.WriteLine("-------")
Console.WriteLine("Worker Thread Reactivated")
Console.WriteLine("After Set in Main Thread")
End Sub
Sub Main()
' true - signaled
' false non-signaled
theEvent = New AutoResetEvent(False)
Console.WriteLine("In Main Thread")
Console.WriteLine("Worker Thread Starts Next")
Dim t As New Thread(AddressOf DoWork)
t.Start()
Thread.Sleep(1000)
Console.WriteLine("-------")
Console.WriteLine("Main Thread Resumes")
Console.WriteLine("Set Changes AutoResetEvent")
Console.WriteLine("Object to Signaled")
theEvent.Set()
Thread.Sleep(1000)
Console.WriteLine("-------")
Console.WriteLine("Main Thread Resumes Again")
Console.ReadLine()
End Sub
The following diagram shows you what takes place when this code is executed:
--------
Click Here to display the illustration
--------
This illustration shows the console while the code runs.
--------
Click Here to display the illustration
--------
Notice the Sleeps in the code. One problem that can plague your code is that there is no guarantee that calls to synchronization methods (Set and WaitOne) will always work because the operating system actually controls thread execution. Statements in your code might get executed out of the order you intend before the operating system actually switches thread execution. The Sleep methods introduce a delay that's long enough to guarantee that the methods actually run. To prove this to yourself, take them out and then run the code. Here's what happened when I ran the same code on my computer with no Sleeps.
--------
Click Here to display the illustration
--------
ManualResetEvent
To illustrate ManualReset, lets's add one more worker thread to the mix. AutoResetEvent changes from signaled to unsignaled automatically when it activates a thread. So, when using AutoResetEvent, a second thread is blocked after a WaitOne method until it's signaled by a later Set method. But with ManualResetEvent, it's not.
'Dim theEvent As AutoResetEvent
Dim theEvent As ManualResetEvent
Sub DoWork1()
Console.WriteLine("-------")
Console.WriteLine("Worker Thread One Started")
Console.WriteLine("theEvent.WaitOne Executed Next")
theEvent.WaitOne()
Console.WriteLine("-------")
Console.WriteLine("Worker Thread One Reactivated")
Console.WriteLine("After Set in Main Thread")
End Sub
Sub DoWork2()
Console.WriteLine("-------")
Console.WriteLine("Worker Thread Two Started")
Console.WriteLine("theEvent.WaitOne Executed Next")
theEvent.WaitOne()
Console.WriteLine("-------")
Console.WriteLine("Worker Thread Two Reactivated")
Console.WriteLine("After Set in Main Thread")
End Sub
Sub Main()
' true - signaled
' false non-signaled
'theEvent = New AutoResetEvent(False)
'Console.WriteLine(" -- AutoReset -- ")
theEvent = New ManualResetEvent(False)
Console.WriteLine(" -- ManualReset -- ")
'theEvent = New ManualResetEvent(False)
Console.WriteLine("In Main Thread")
Console.WriteLine("Worker Thread One Starts Next")
Dim t1 As New Thread(AddressOf DoWork1)
t1.Start()
Thread.Sleep(1000)
Dim t2 As New Thread(AddressOf DoWork2)
t2.Start()
Thread.Sleep(1000)
Console.WriteLine("-------")
Console.WriteLine("Main Thread Resumes")
Console.WriteLine("Set Changes Event")
Console.WriteLine("Object to Signaled")
theEvent.Set()
Thread.Sleep(1000)
Console.WriteLine("-------")
Console.WriteLine("Main Thread Resumes Again")
Console.ReadLine()
End Sub
The side by side results of both shown below demonstrate that AutoResetEvent blocks the second worker thread but ManualResetEvent does not.
--------
Click Here to display the illustration
--------
To return to the first problem, deadlocks, if a worker thread needs a resource ... for example, exclusive access to a file ... that another thread also needs, then you can place a "wait" method in front of the file access statements. The main thread can monitor completion of threads and execute Set statements that release the blocks in the worker threads and avoid deadlocks.
These two events have lots of other methods to get confused about. For example, Set changes the event to signaled, but Reset changes it to nonsignaled. A note in the MSDN documentation for the ManualResetEvent and AutoResetEvent Classes mentions that WaitAny and WaitAll can be used in addition to WaitOne, but they're not mentioned in the "Methods" section for reasons that just aren't clear to me. In any case, the next example in this series, Threading - Using the ThreadPool Class, shows an example from a different part of the MSDN documentation using WaitAll and allocating threads from the built in ThreadPool.
