Multithreading in VB.NET

WaitHandle, AutoResetEvent and ManualResetEvent Classes

We will now examine a MustInherit type class, WaitHandle. WaitHandle 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. Pulse. ManualResetEvent 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.

You might also like...

Comments

About the author

John Spano United States

John Spano cofounder and CTO of NeoTekSystems, a Greenville, South Carolina technology consulting company. NeoTekSystems offers IT consulting, custom programming, web design and web hosting. We ...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“PHP is a minor evil perpetrated and created by incompetent amateurs, whereas Perl is a great and insidious evil perpetrated by skilled but perverted professionals.” - Jon Ribbens