VBNET:DataSharingMultithreading

From GPWiki

Files:GUITutorial_warn.gif The Game Programming Wiki has moved! Files:GUITutorial_warn.gif

The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server.

However, the GPWiki forums are still active! Come say hello.

Introduction

A big problem with multithreaded applications is data sharing. If you look at file manipulations, the I/O routines lock the target file to prevent other processes from modifying the data while the current process is modifying it. If it didn't there would be data corruption and such (one process overwrites what another just wrote, so that they conflict later). The same can happen in multithreaded applications. To prevent this, you lock your variables as well, using the SyncLock statement. As an explanation, MSDN says it best: Quote: Monitor objects are used to ensure that a block of code runs without being interrupted by code running on other threads. In other words, code in other threads cannot run until code in the synchronized code block has finished.


An Example

Suppose, for example, that you have a program that repeatedly and asynchronously reads data and displays the results. With operating systems that use preemptive multitasking, a running thread can be interrupted by the operating system to allow time for some other thread to run. Without synchronization, it is possible that you could get a partially updated view of the data if the object that represents the data is modified by another thread while the data is being displayed. Monitor objects guarantee that a section of code will run without being interrupted. Visual Basic .NET provides the SyncLock and End SyncLock statements to simplify access to monitor objects.


SyncLock

SyncLock puts an exclusive lock on the object that is passed to it, preventing other threads from accessing it. SyncLock must be passed a reference object, and a handy one is the System.Type of the class itself. Here is how it would look in our example:

Public Class FillClass
  Private intValue As Int32
  Private intReturn(99) As Int32
  Private intMultiplier As Int32
 
  Public WriteOnly Property Value()
    Set(ByVal Value As Int32)
      intValue = Value
    End Set
  End Property
 
  Public WriteOnly Property Multiplier()
    Set(ByVal Value As Int32)
      intMultiplier = Value
    End Set
  End Property
 
  Public ReadOnly Property Return()
    Get
      Return = intReturn
    End Get
  End Property
 
  Public ReadOnly Property ReturnIndex(ByVal Index As Int32)
    Get
      Return = intReturn(index)
    End Get
  End Property
 
  Public Function Fill()
    SyncLock GetType(FillClass)
      Dim i As Int32
      Dim current As Int32
  
      current = intValue
      For i = 0 To 100
        intReturn(i) = current
        current *= intMultiplier
      Next
    End SyncLock
  End Function
End Class

For instance, if this code would be run without a SyncLock, and another thread modifies intMultiplier (by using the property Multiplier) while this thread was running, the results would be incorrect. SyncLock prevents other processes from accessing the data until the block ends.


Thread.Join

Now, for a more complex problem using Thread.Join as mentioned earlier. Join is a method that allows you to waith till a thread completes before proceeding (blocks the current thread). It can also be passed a number as an argument. It waits that number in milliseconds, then returns true if the thread is finished, or false if not.

In this example, we are going to spawn two new threads, then make another to compute the results from the previous two.

Dim oThread(2) As System.Threading.Thread
Dim oFill(2) As New FillClass
 
'start the first thread, setting 2 as the value
oThread(0) = New Thread(AddressOf oFill(0).Fill)
oFill(0).Value = 2
oFill(0).Multiplier = 2
oThread(0).Start()
 
'start the second, using 3 as the value
oThread(1) = New Thread(AddressOf oFill(1).Fill)
oFill(1).Value = 3
oFill(1).Multiplier = 2
oThread(1).Start()
 
'wait until the first thread is done
oThread(0).Join
'waith until the second thread is done
oThread(1).Join
 
oThread(2) = New Thread(AddressOf oFill(2).Fill)
oFill(2).Value = oThread(0).ReturnIndex(4) - oThread(1).ReturnIndex
oFill(2).Multiplier = 2
oThread(2).Start()
 
oThread(2).Join
Dim i As Int32, retVal() As Int32
 
retVal = oThread(2).Return
For i = 0 To UBound(retVal)
  Listbox1.Items.Add(retVal(i))
Next

Ok, here's what happened. First, we declare two an array of threads, and an array of classes. Next we set the first thread to the first class' function, then fill the value, and finally start it. We do the same for the next one. Then, we call the oThread(0).Join, which will block until the thread finishes its execution. We do the same for the next thread, ensuring they are both done. Now we create a new thread and set its value, which is based on the previous two results (which is why we had to wait for the threads to finish execution, so we could use their values). We run, block until its done, then display the results in a Listbox.