For any computer programmer, threads, like pointers, are one of those hurdles that seem to take forever to climb over. No matter how many times you read that passage in the book or the notes from class, it seems that the day of understanding will never come...until one day, the key log breaks in two and everything becomes clear. Here's an explanation of my own on the concept written in VB.NET-ese that, if you're having trouble like I did, maybe will get the logs flowing again for you.

A Brief Recap

All those programs running on your computer right now (your browser, your media player, your virus scanner, all that spyware) are threads. You see, your computer may appear to be running all of those programs simultaneously, but really, the thing can only run one program at a time. What the programs do is take turns running. And they do this really really really quickly, which makes it seem like everything's running all at once.

The term "thread" is just a word for a block of computer code that needs to be run independently of other blocks of computer code. Each program doing its thing on your information machine is what's called a "process". And within each of those processes, there is at least one, but usually more than one, thread running. For example, when visiting a page in, say, MySpace, you might be dazzled by twenty different animating images, startled by some loud noise that sounds like music, and frenzied into scrolling down the page all at once. A thread has to be created for each of those actions inside your browser in order for these things to happen at once. What I'm going to explain here is how to write code for a VB.NET program (a process) which creates its own threads.

To the spinning wheel

First off, you're going to want to always add the System.Threading import to whatever class you'll be creating threads in. Without that at the top of your code, nothing will work. That being said, there are two main ways that you can actually start a thread in VB.NET.
  • Use the Thread class
    Dim someThread As New Thread(AddressOf SomeMethod)
    someThread.Start()

    This spawns a totally new thread, executing the code inside the SomeMethod method (which you define yourself). Don't forget that second line of code or else the thread will never start running and you'll be spending hours trying to figure out why (first-hand experience here). Also note that you could also do something like this:

    Dim someClass As New SomeClass
    Dim someThread As New Thread(AddressOf someClass.SomeMethod)
    someThread.Start()

    This will spawn a thread which runs inside the individual instance of that class.
     
  • All yarn outta the pool
    Dim someObject as New Object
    ThreadPool.QueueUserWorkItem(AddressOf SomeMethod, someObject)

    The ThreadPool class is basically a list that manages up to twenty-five different threads. All threads the pool have a priority of "normal" (I'll explain more about that soon), which cannot be changed. From what I've read, it's suggested that you use this technique instead of the latter unless you're desperate for more control over your code.
     
  • Note: Thread is NonInheritable
    For any Java programmers out there, VB.NET does not allow you to create a class which inherits Thread. Being from a Java background myself, this absolutely bedazzled me.

"It's either the car or ME!"

Each thread running on your box has a priority. Those with a high priority get to take turns more often than those with a low priority. VB.NET lets you control the priority of your threads. The exception to this is the threads that are added to the ThreadPool; their priorities are set in stone.

Dim someThread as New Thread(AddressOf SomeMethod)
someThread.Priority = ThreadPriority.Highest

Use the ThreadPriority enumeration to control how often the thread is allowed to run. The default setting for this property is ThreadPriority.Normal and it's suggested that, unless you know what you're doing, you leave it that way.

Dim someThread as New Thread(AddressOf SomeMethod)
someThread.IsBackground = True

In addition, you can define your thread as a "background" thread. Threads by default are not background threads so the programmer is responsible for stopping any threads he created when the program exits (or else the program will never really exit). If this property is set to True, then the thread will be automatically killed when the program exits. This can be a good thing or a bad thing, depending on what your thread is doing, so keep an eye out.

Left, one two three, right one two three

Synchronization is by far the more difficult aspect of threading to wrap one's mind around. If you have two threads, each executing the same code inside the same exact method, and that method's code modifies data which is shared by those threads, then you run into a big problem. You have no control over when the thread A gives up its turn to thread B. Thread A could be in the middle of a calculation and thread B could start running, modifying one of the shared variables, thereby ruining the result of thread A's calculation. The programmer must tell the computer where these critical sections of code are--sections where the thread must never relinquish its control to another thread.

It accomplishes this by using what's called a "lock." If a thread reaches critical code which is protected by a lock, it waits until the lock is available before taking possession of it and running the critical code. When it finishes executing the code, the thread then releases the lock so that another thread that is waiting to run the code now can.

Now, as you might expect, being the creatures of free will that we all are, we have many choices at our disposal with which to implement thread synchronization (in VB.NET anyway).

The first set of choices are the simplest:
  1. Dim lockObject As New Object
    Sub SomeMethod()
         SyncLock lockObject
              'critical section goes here
         End SyncLock
    End Sub

    This is the simplest way of accomplishing synchronization. The lockObject must be (1) not equal to Nothing and (2) a reference object (i.e. not a primitive like Integer or Double).
     
  2. Imports System.Runtime.CompilerServices
    Class SomeClass
         <MethodImpl(MethodImplOptions.Synchronized)> _
         Sub SomeMethod()
              'critical code
         End Sub
    End Class

    The mess of code you see above makes the entire method synchronized. It's just like encasing all the method's code in a SyncLock block, but without having to declare a special object to act as the lock. If you have multiple methods in the same class using this, they each share the same lock object behind the scenes so only one of the synchronized methods can be accessed at a time.
     
  3. Imports System.Runtime.Remoting.Contexts
    <Synchronization()> _
    Class SomeClass
         Inherits ContextBoundObject
         'everything is critical!
    End Class

    This makes everything inside your entire class critical, which is nice if you want an easy way to avoid synchronization bugs. However, from what I've read, that's usually overkill. Plus, you're forced to inherit some stupid object.
This next set of choices are class-based and give you more control. But with that control comes responsibility. You must always remember to manually release the lock when you are finished...it won't be done automatically for you like the above implementations. Because of this, it's often good to enclose the lock/unlock method calls in a Try/Finally block to ensure that the lock will always be released if the code decides to throw an exception and exit the method prematurely.

Note: Before many of the examples, I'm going to give a few equivalences (which are not code themselves) which compare the functionality of the class under discussion to previously discussed synchronization techniques.
  1. Dim lockObject As New Object
    Monitor.Enter(lockObject) === SyncLock lockObject
    Monitor.Exit(lockObject) === End SyncLock

    Dim lockObject As New Object
    Sub SomeMethod()
         If Monitor.TryEnter(lockObject) Then
              Try
                   'you've obtained the lock and have entered the critical section
              Finally
                   Monitor.Exit(lockObject)
              End Try
         Else
              'No lock for you!
         End If
    End Sub

    The Monitor object contains all the functionality of the SyncLock block plus an additional feature. The TryEnter() shared method returns True and acquires the lock if the lock can be acquired or False if the lock cannot be acquired (it will not wait to acquire the lock like the Enter() method does).
     
  2. Dim lockCounter As Integer
    Sub SomeMethod()
         Try
              If Interlocked.Increment(lockCounter) <= 4 Then
                   'critical section
              Else
                   'there are more than four threads in the critical section...no lock for you!
              End If
         Finally
              Interlocked.Decrement(lockCounter)
         End Try
    End Sub

    This class acts like Monitor.TryEnter() in that it will try to obtain the lock, but won't wait for it. What is unique here is the fact that you can control just how many threads are allowed to access the critical section. The example above allows a max of four threads inside at once. Since you're only incrementing and decrementing an integer, I don't know why you need an entire class to do this, but I could be missing something.
     
  3. Dim someMutex As New Mutex
    Dim lockObject As New Object
    someMutex.WaitOne() === Synclock lockObject
    someMutex.ReleaseMutex() === End Synclock
    someMutex.WaitOne(100, False) === Monitor.TryEnter(lockObject, 100)

    In addition to the equivalences you see above, the Mutex class has two handy shared methods:

    Dim someMutexes() As Mutex = {New Mutex, New Mutex, New Mutex}
    Sub SomeMethod()
         Dim someInteger As Integer
         someInteger = Mutex.WaitAny(someMutexes)
         Try
              'critical section
         Finally
              someMutexes(someInteger).ReleaseMutex()
         End Try
    End Sub

    This will cause the thread to wait until one of the Mutex objects in the array is released, at which point it returns the array index of the Mutex, acquires it, and moves on.

    Dim someMutexes() As Mutex = {New Mutex, New Mutex, New Mutex}
    Sub SomeMethod()
         Mutex.WaitAll(someMutexes)
         'all Mutex objects are now released
    End Sub

    This waits until all Mutex objects in the array are released. I guess this could be good for ensuring that all your threads complete before your program terminates.

    Dim someMutex As New Mutex("False", "some name")

    Another feature of this class lies in the programmer's ability to assign a name to each Mutex he creates. The spiffy thing about this is that if a Mutex exists anywhere in the system (not just in your program) with that name, it becomes associated with that Mutex.

And I'm spent

And so ends my tutorial on threading in VB.NET. For more specific knowledge on how the VB.NET code works, take a look at the MSDN documentation. Again, threads take some time to get used to, but once understood, I guarantee the ladies will be all over you.

References:

  • Balena, Francesco. Programming Microsoft Visual Basic .NET (Version 2003). Microsoft Press. 2004.
  • MSDN Documentation. <http://msdn.microsoft.com/vbasic/>

Swap asks, "Do you know how this works within the Mono framework?"

I have absolutely zero experience with Mono, but from how fabulous I've heard it's supposed to be, I would think that the things I discussed in my node would apply to it as well. The only exception I can think of is concerning mutexes. I'm taking a wild guess here, but they might be a Windows-only thing, and since they seem to be tied really close to the O/S, Mono (for *nix) might handle them differently. Perhaps someone could correct me?

Thanks to rootbeer277 and call for pointing out some typos.

Log in or register to write something here or to contact authors.