Export (0) Print
Expand All
Around the World with Visual Basic
Asynchronous Method Execution Using Delegates
Building a Progress Bar that Doesn't Progress
Calling All Operators
Create a Graphical Editor Using RichTextBox and GDI+
Creating A Breadcrumb Control
Creating a Five-Star Rating Control
Creating and Managing Secondary Threads
Data Binding Radio Buttons to a List
Deploying Assemblies
Designing With Custom Attributes
Digital Grandma
Doing Async the Easy Way
Extracting Data from .NET Assemblies
Implementing Callbacks with a Multicast Delegate
Naming and Building Assemblies in Visual Basic .NET
Programming Events of the Framework Class Libraries
Programming I/O with Streams in Visual Basic .NET
Reflection in Visual Basic .NET
Remembering User Information in Visual Basic .NET
Advanced Basics: Revisiting Operator Overloading
Scaling Up: The Very Busy Background Compiler
Synchronizing Multiple Windows Forms
Thread Synchronization
Updating the UI from a Secondary Thread
Using Inheritance in the .NET World
Using the ReaderWriterLock Class
Visual Basic: Simplify Common Tasks by Customizing the My Namespace
What's My IP Address?
Windows Forms Controls: Z-order and Copying Collections
Expand Minimize

Multithreaded Programming with Visual Basic .NET

Visual Studio .NET 2003
 

Robert Burns
Visual Studio Team
Microsoft Corporation

February 2002

Summary: The .NET Framework provides new classes that make it easy to create multithreaded applications. This article discusses how you can use multithreaded programming techniques with Visual Basic® .NET to develop more efficient and responsive applications. (18 printed pages)

Contents

Introduction
Advantages of Multithreaded Processing
Creating New Threads
Synchronizing Threads
Thread Timers
Canceling Tasks
Conclusions

Introduction

Traditionally, Visual Basic developers have created synchronous applications in which program tasks execute sequentially. Although multithreaded applications can be more efficient because multiple tasks run more or less simultaneously, they were difficult to create using previous versions of Visual Basic.

Multithreaded programs are possible because of an operating system feature called multitasking that simulates the ability to run multiple applications at the same time. Although most personal computers only have a single processor, modern operating systems provide multitasking by dividing processor time between multiple pieces of executable code called threads. A thread can represent an entire application, but more often it represents just one part of an application that can run separately. The operating system allocates processing time for each thread based on factors such as the thread's priority and how much time has passed since the thread last ran. Multiple threads can greatly improve performance when executing time-intensive tasks such as file input and output.

There is one cautionary note. Although multiple threads can improve performance, each thread has an associated cost in terms of additional memory required to create the thread and processor time required to keep it running. Creating too many threads can actually reduce your application's performance. When designing multithreaded applications, you should weigh the benefits of adding additional threads against their cost.

Multitasking has been a part of operating systems for a while. Until recently, however, Visual Basic programmers could only perform multithreaded tasks by using undocumented features or indirectly by using COM components or asynchronous parts of the operating system. The .NET Framework provides comprehensive support in the System.Threading namespace for developing multithreaded applications.

This article discusses some of the advantages of multithreading and how you can use Visual Basic .NET to develop multithreaded applications. Although Visual Basic .NET and the .NET Framework make it easy to develop multithreaded applications, this paper is intended for moderate to advanced developers as well as developers who are transitioning from earlier versions of Visual Basic to Visual Basic .NET. If you are just getting started using Visual Basic .NET, you should first read the topics in Visual Basic Language Tour.

While it is beyond the scope of this article to comprehensively discuss multithreaded programming, you may find more information in the additional resources at the end of this article.

Advantages of Multithreaded Processing

Although synchronous applications are easier to develop, they are often less efficient than multithreaded applications because a new task cannot start until a previous task has finished. If completion of a synchronous task takes longer than expected, your application may appear unresponsive. Multithreaded processing can simultaneously run multiple procedures. For example, a word processor application can check spelling as a separate task while you continue to work on the document. Because multithreaded applications divide programs into independent tasks, they can substantially improve performance in the following ways:

  • Multithreaded techniques can make your program more responsive because the user interface can remain active while other work continues.
  • Tasks that are not currently busy can yield processor time to other tasks.
  • Tasks that use a lot of processing time can periodically yield to other tasks.
  • Tasks can be stopped at any time.
  • You can set the priority of individual tasks higher or lower to optimize performance.

The decision to explicitly create a multithreaded application depends on several factors. Multithreading is best suited to situations where:

  • Time-consuming or processor-intensive tasks block the user interface.
  • Individual tasks must wait for an external resource, such as a remote file or Internet connection.

For example, consider an Internet "robot", an application that follows links on Web pages and downloads files that meet specific criteria. Such an application can either synchronously download each file one after another or use multithreading to download multiple files at the same time. The multithreaded approach can be much more efficient than the synchronous approach because files can be downloaded even when some threads receive slow responses from remote Web servers.

Creating New Threads

The most straightforward way to create a thread is by creating a new instance of the thread class and using the AddressOf statement to pass a delegate for the procedure you want to run. For example, the following code runs a sub procedure named SomeTask as a separate thread.

Dim Thread1 As New System.Threading.Thread(AddressOf SomeTask)
Thread1.Start
' Code here runs immediatly.

That is all there is to creating and starting a thread. Any code that follows a call to the thread's Start method runs immediately without waiting for the previous thread to finish.

The following table shows some of the methods you can use to control individual threads.

Method Action
Start Causes a thread to start running.
Sleep Pauses a thread for a specified time.
Suspend Pauses a thread when it reaches a safe point.
Abort Stops a thread when it reaches a safe point.
Resume Restarts a suspended thread
Join Causes the current thread to wait for another thread to finish. If used with a timeout value, this method returns True if the thread finishes in the allotted time.

Most of these methods are self explanatory, but the concept of safe points may be new to you. Safe points are places in code where it is safe for the common language runtime to perform automatic garbage collection, the process of releasing unused variables and reclaiming memory. When you call the Abort or Suspend methods of a thread, the common language runtime analyzes the code and determines the location of an appropriate place for the thread to stop running.

Threads also contain a number of useful properties, as shown in the following table:

Property Value
IsAlive Contains the value True if a thread is active.
IsBackground Gets or sets a Boolean that indicates if a thread is or should be a background thread. Background threads are like foreground threads, but background threads do not prevent a process from terminating. Once all foreground threads belonging to a process have terminated, the common language runtime ends the process by calling the Abort method on background threads that are still alive.
Name Gets or sets the name of a thread. Most commonly used to discover individual threads when debugging.
Priority Gets or sets a value used by the operating system to prioritize thread scheduling.
ApartmentState Gets or sets the threading model used for a particular thread. Threading models are important when a thread calls unmanaged code.
ThreadState Contains a value that describes a thread's state or states.

Thread properties and methods are useful when creating and managing threads. The thread synchronization section of this article discusses how you can use these properties and methods to control and coordinate threads.

Thread Arguments and Return Values

The method call in the previous example cannot contain any parameters or return values. This limitation is one of the main drawbacks to creating and running threads in this way. However, you can provide and return arguments for procedures running on separate threads by wrapping them in either a class or a structure.

Class TasksClass
   Friend StrArg As String
   Friend RetVal As Boolean
   Sub SomeTask()
      ' Use the StrArg field as an argument.
      MsgBox("The StrArg contains the string " & StrArg)
      RetVal = True ' Set a return value in the return argument.
   End Sub
End Class
' To use the class, set the properties or fields that store parameters,
' and then asynchronously call the methods as needed.
Sub DoWork()
   Dim Tasks As New TasksClass()
   Dim Thread1 As New System.Threading.Thread( _
       AddressOf Tasks.SomeTask)
   Tasks.StrArg = "Some Arg" ' Set a field that is used as an argument
   Thread1.Start() ' Start the new thread.
   Thread1.Join() ' Wait for thread 1 to finish.
   ' Display the return value.
   MsgBox("Thread 1 returned the value " & Tasks.RetVal)
End Sub

Manually creating and managing threads is best suited to applications in which you want fine control of details such as thread priority and threading model. As you can imagine, it can be difficult to manage large numbers of threads in this way. Consider using thread pooling to reduce complexity when you need many threads.

Thread Pooling

Thread pooling is a form of multithreading where tasks are added to a queue and automatically started as threads are created. With thread pooling, you call the Threadpool.QueueUserWorkItem method with a delegate for the procedure you want to run, and Visual Basic .NET creates the thread and runs your procedure. The following example shows how you could use thread pooling to start several tasks.

Sub DoWork()
   Dim TPool As System.Threading.ThreadPool
   ' Queue a task
   TPool.QueueUserWorkItem(New System.Threading.WaitCallback _
                          (AddressOf SomeLongTask))
   ' Queue another task
   TPool.QueueUserWorkItem(New System.Threading.WaitCallback _
                          (AddressOf AnotherLongTask))
End Sub

Thread pooling is useful when you want to start many separate tasks without individually setting the properties of each thread. Each thread starts with a default stack size and priority. By default, up to 25 thread pool threads can run per system processor. Additional threads in excess of the limit can be queued, but they do not start until other threads finish.

One advantage of thread pooling is that you can pass arguments in a state object to the task procedure. If the procedure you are calling requires more than one argument, you can cast a structure or an instance of a class into an Object data type.

Parameters and Return Values

Returning values from a thread pool thread is a little tricky. The standard way of returning values from a function call is not allowed because Sub procedures are the only type of procedure that can be queued to a thread pool. One way you can provide parameters and return values is by wrapping the parameters, return values, and methods in a wrapper class as described in Thread Arguments and Return Values. An easer way to provide parameters and return values is by using the optional ByVal state object variable of the QueueUserWorkItem method. If you use this variable to pass a reference to an instance of a class, the members of the instance can be modified by the thread pool thread and used as return values. At first it may not be obvious that you can modify an object referred to by a variable that is passed by value. This is possible because only the object reference is passed by value. When you make changes to members of the object referred to by the object reference, the changes apply to the actual class instance.

Structures cannot be used to return values inside state objects. Because structures are value types, changes that the asynchronous process makes do not change the members of the original structure. Use structures to provide parameters when return values are not needed.

Friend Class StateObj
   Friend StrArg As String
   Friend IntArg As Integer
   Friend RetVal As String
End Class

Sub ThreadPoolTest()
   Dim TPool As System.Threading.ThreadPool
   Dim StObj1 As New StateObj()
   Dim StObj2 As New StateObj()
   ' Set some fields that act like parameters in the state object.
   StObj1.IntArg = 10
   StObj1.StrArg = "Some string"
   StObj2.IntArg = 100
   StObj2.StrArg = "Some other string"
   ' Queue a task
   TPool.QueueUserWorkItem(New System.Threading.WaitCallback _
                          (AddressOf SomeOtherTask), StObj1)
   ' Queue another task
   TPool.QueueUserWorkItem(New System.Threading.WaitCallback _
                          (AddressOf AnotherTask), StObj2)
End Sub

Sub SomeOtherTask(ByVal StateObj As Object)
   ' Use the state object fields as arguments.
   Dim StObj As StateObj
   StObj = CType(StateObj, StateObj)   ' Cast to correct type.
   MsgBox("StrArg contains the string " & StObj.StrArg)
   MsgBox("IntArg contains the number " & CStr(StObj.IntArg))
   ' Use a field as a return value.
   StObj.RetVal = "Return Value from SomeOtherTask"
End Sub

Sub AnotherTask(ByVal StateObj As Object)
   ' Use the state object fields as arguments.
   ' The state object is passed as an Object.
   ' Casting it to its specific type makes it easier to use.
   Dim StObj As StateObj
   StObj = CType(StateObj, StateObj)
   MsgBox("StrArg contains the String " & StObj.StrArg)
   MsgBox("IntArg contains the number " & CStr(StObj.IntArg))
   ' Use a field as a return value.
   StObj.RetVal = "Return Value from AnotherTask"
End Sub

The common language runtime automatically creates threads for queued thread pool tasks and then releases these resources when the tasks are done. There is no easy way to cancel a task once it has been queued. ThreadPool threads always run using the multithreaded apartment (MTA) threading model. You should manually create threads if you want threads that use the single threaded apartment model (STA).

Synchronizing Threads

Synchronization provides a compromise between the unstructured nature of multithreaded programming and the structured order of synchronous processing.

You use synchronization techniques:

  • To explicitly control the order in which code runs whenever tasks must be performed in a specific sequence

    -or-

  • To prevent the problems that can occur when two threads share the same resource at the same time.

For example, you could use synchronization to cause a display procedure to wait until a data retrieval procedure that is running on another thread is complete.

There are two approaches to synchronization, polling and using synchronization objects. Polling repeatedly checks the status of an asynchronous call from within a loop. Polling is the least efficient way to manage threads because it wastes resources by repeatedly checking the status of the various thread properties.

For example, the IsAlive property can be used when polling to see if a thread has exited. Use this property with caution because a thread that is alive is not necessarily running. You can use the thread's ThreadState property to get more detailed information about a thread's status. Because threads can be in more than one state at any given time, the value stored in ThreadState can be a combination of the values in the System.Threading.Threadstate enumeration. Consequently, you should carefully check all relevant thread states when polling. For example, if a thread's state indicates that it is not Running, it may be done. On the other hand, it may be suspended or sleeping.

As you can imagine, polling sacrifices some of the advantages of multithreading in return for control over the order that threads run. A more efficient approach would use the Join method to control threads. Join causes a calling procedure to wait either until a thread is done or until the call times out if a timeout is specified. The name, join, is based on the idea that creating a new thread is a fork in the execution path. You use Join to merge separate execution paths into a single thread again.

Figure 1 Threading

One point should be clear: Join is a synchronous or blocking call. Once you call Join or a wait method of a wait handle, the calling procedure stops and waits for the thread to signal that it is done.

Sub JoinThreads()
   Dim Thread1 As New System.Threading.Thread(AddressOf SomeTask)
   Thread1.Start()
   Thread1.Join()      ' Wait for the thread to finish.
   MsgBox("Thread is done")
End Sub

These simple ways of controlling threads, which are useful when you are managing a small number of threads, are difficult to use with large projects. The next section discusses some advanced techniques you can use to synchronize threads.

Advanced Synchronization Techniques

Multithreaded applications often use wait handles and monitor objects to synchronize multiple threads. The following table describes some of the .NET Framework classes that can be used when synchronizing threads.

Class Purpose
AutoResetEvent A wait handle that notifies one or more waiting threads that an event has occurred. AutoResetEvent automatically changes status to signaled when a waiting thread is released.
Interlocked Provides atomic operations for variables that are shared by multiple threads.
ManualResetEvent A wait handle that notifies one or more waiting threads that an event has occurred. The state of a manually reset event remains signaled until the Reset method sets it to the nonsignaled state. Similarly, the state remains nonsignaled until the Set method sets it to the signaled state. Any number of waiting threads, or threads that subsequently begin wait operations for the specified event object by calling one of the wait functions, can be released while the object's state is signaled.
Monitor Provides a mechanism that synchronizes access to objects. Visual Basic .NET applications call SyncLock to use monitor objects.
Mutex A wait handle that can be used for interprocess synchronization.
ReaderWriterLock Defines the lock that implements single-writer and multiple-reader semantics.
Timer Provides a mechanism for running tasks at specified intervals.
WaitHandle Encapsulates operating system-specific objects that wait for exclusive access to shared resources.

Wait Handles

Wait handles are objects that signal the status of one thread to another thread. Threads can use wait handles to notify other threads that they need exclusive access to a resource. Other threads must then wait to use this resource until the wait handle is no longer in use. Wait handles have two states, signaled and nonsignaled. A wait handle that is not owned by any thread is in the signaled state. A wait handle that is owned by a thread is in the nonsignaled state.

Threads request ownership of a wait handle by calling one of the wait methods, such as WaitOne, WaitAny, or WaitAll. The wait methods are blocking calls that are similar to the Join method of an individual thread.

  • If no other thread owns the wait handle, the call immediately returns True, the wait handle's status is changed to nonsignaled, and the thread that owns the wait handle continues to run.
  • If a thread calls one of a wait handle's wait methods, but the wait handle is owned by another thread, the calling thread will either wait for a specified time (if a timeout is specified) or wait indefinitely (if no timeout is specified) for the other thread to release the wait handle. If a timeout is specified and the wait handle is released before the timeout expires, the call returns True. Otherwise, the call to wait returns False, and the calling thread continues to run.

Threads that own a wait handle call the Set method when they are done or when they no longer need the wait handle. Other threads can reset the status of a wait handle to nonsignaled by either calling the Reset method or by calling WaitOne, WaitAll, or WaitAny and successfully waiting for a thread to call Set. AutoResetEvent handles are automatically reset to nonsignaled by the system after a single waiting thread has been released. If no threads are waiting, the event object's state remains signaled.

Method Purpose
WaitOne Accepts a wait handle as an argument and causes the calling thread to wait until the current wait handle is signaled by another thread that calls Set.
WaitAny Accepts an array of wait handles as an argument and causes the calling thread to wait until any one of the specified wait handles signal by calling Set.
WaitAll Accepts an array of wait handles as an argument and causes the calling thread to wait until all specified wait handles signal by calling Set.
Set Sets the state of the specified wait handle to signaled and causes any waiting threads to resume.
Reset Sets the state of the specified event to nonsignaled.

There are three kinds of wait handles commonly used with Visual Basic .NET: mutex objects, ManualResetEvent, AutoResetEvent. The last two are often referred to as synchronization events.

Mutex Objects

Mutex objects are synchronization objects that can only be owned by a single thread at a time. In fact, the name, mutex, is derived from the fact that ownership of mutex objects is mutually exclusive. Threads request ownership of the mutex object when they require exclusive access to a resource. Because only one thread can own a mutex object at any time, other threads must wait for ownership of a mutex object before using the resource.

The WaitOne method causes a calling thread to wait for ownership of a mutex object. If a thread terminates normally while owning a mutex object, the state of the mutex object is set to signaled and the next waiting thread gets ownership.

Synchronization Events

Synchronization events are used to notify other threads that something has occurred or that a resource is available. Do not be fooled by the fact that these items use the word event. Synchronization events, which are unlike other Visual Basic events, are really wait handles. Like other wait handles, synchronization events have two states, signaled and nonsignaled. Threads that call one of the wait methods of a synchronization event must wait until another thread signals the event by calling the Set method. There are two synchronization event classes. Threads set the status of ManualResetEvent instances to signaled using the Set method. Threads set the status of ManualResetEvent instances to nonsignaled using the Reset method or when control returns to a waiting WaitOne call. Instances of the AutoResetEvent class can also be set to signaled using Set, but they automatically return to nonsignaled as soon as a waiting thread is notified that the event became signaled.

The following example uses the AutoResetEvent class to synchronize thread-pool tasks.

Sub StartTest()
   Dim AT As New AsyncTest()
   AT.StartTask()
End Sub

Class AsyncTest
   Private Shared AsyncOpDone As New _
      System.Threading.AutoResetEvent(False)

   Sub StartTask()
      Dim Tpool As System.Threading.ThreadPool
      Dim arg As String = "SomeArg"
      Tpool.QueueUserWorkItem(New System.Threading.WaitCallback( _
         AddressOf Task), arg)  ' Queue a task.
      AsyncOpDone.WaitOne() ' Wait for the thread to call Set.
      MsgBox("Thread is done.")
   End Sub

   Sub Task(ByVal Arg As Object)
      MsgBox("Thread is starting.")
      System.Threading.Thread.Sleep(4000) ' Wait 4 seconds.
      MsgBox("The state object contains the string " & CStr(Arg))
      AsyncOpDone.Set()   ' Signal that the thread is done.
   End Sub
End Class

Monitor Objects and SyncLock

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. The SyncLock keyword is used to simplify access to monitor objects in Visual Basic .NET. Visual C#® .NET uses the Lock keyword in the same way.

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. The SyncLock statement guarantees that a section of code will run without being interrupted. The following example shows how you could use SyncLock to provide the display procedure with exclusive access to a data object.

Class DataObject
   Public ObjText As String
   Public ObjTimeStamp As Date
End Class

Sub RunTasks()
   Dim MyDataObject As New DataObject()
   ReadDataAsync(MyDataObject)
   SyncLock MyDataObject
      DisplayResults(MyDataObject)
   End SyncLock
End Sub

Sub ReadDataAsync(ByRef MyDataObject As DataObject)
   ' Add code to asynchronously read and process the data.
End Sub

Sub DisplayResults(ByVal MyDataObject As DataObject)
   ' Add code to display the results.
End Sub

Use SyncLock when you have a section of code that should not be interrupted by code running on a separate thread.

Interlocked Class

You can use the methods of the Interlocked class to prevent problems that can occur when multiple threads attempt to simultaneously update or compare the same value. The methods of this class let you safely increment, decrement, exchange, and compare values from any thread. The following example shows how to use the Increment method to increment a variable that is shared by procedures running on separate threads.

Sub ThreadA(ByRef IntA As Integer)
   System.Threading.Interlocked.Increment(IntA)
End Sub

Sub ThreadB(ByRef IntA As Integer)
   System.Threading.Interlocked.Increment(IntA)
End Sub

ReaderWriter Locks

In some cases, you may want to lock a resource only when data is being written and permit multiple clients to simultaneously read data when data is not being updated. The ReaderWriterLock class enforces exclusive access to a resource while a thread is modifying the resource, but it allows non-exclusive access when reading the resource. ReaderWriter locks are a useful alternative to exclusive locks that cause other threads to wait, even when those threads do not need to update data. The following example shows how you can use ReaderWriter to coordinate read and write operations from multiple threads.

Class ReadWrite
'The ReadData and WriteData methods can be safely called from multiple 
' threads.
   Public ReadWriteLock As New System.Threading.ReaderWriterLock()
   Sub ReadData()
      ' This procedure reads information from some source.
      ' The read lock prevents data from being written until the thread 
      ' is done reading, while allowing other threads to call ReadData.
      ReadWriteLock.AcquireReaderLock(System.Threading.Timeout.Infinite)
      Try
         ' Perform read operation here.
      Finally
         ReadWriteLock.ReleaseReaderLock() ' Release the read lock.
      End Try
   End Sub

   Sub WriteData()
      ' This procedure writes information to some source.
      ' The write lock prevents data from being read or written until
      ' the thread is done writing.
      ReadWriteLock.AcquireWriterLock(System.Threading.Timeout.Infinite)
      Try
         ' Perform write operation here.
      Finally
         ReadWriteLock.ReleaseWriterLock() ' Release the write lock.
      End Try
   End Sub
End Class

Deadlocks

Thread synchronization is invaluable in multithreaded applications, but there is always the danger of deadlocks where multiple threads wait for each other. Analogous to a situation in which cars are stopped at a four-way stop and each person is waiting for another to go, deadlocks bring everything to a halt. Needless to say, avoiding deadlocks is important. There are many ways to get into deadlock situations and almost as many ways to avoid them. Although there is not enough space in this article to cover all the issues related to deadlocks, the important point is that careful planning is key to avoiding them. You can often predict deadlock situations by diagramming multithreaded applications before you start coding.

Thread Timers

The Threading.Timer class is useful to periodically run a task on a separate thread. For example, you could use a thread timer to check the status and integrity of a database or to back up critical files. The following example starts a task every two seconds and uses a flag to initiate the Dispose method that stops the timer. This example posts status to the output window, so you should make this window visible by pressing Control+Alt+O before you test the code.

Class StateObjClass
' Used to hold parameters for calls to TimerTask
      Public SomeValue As Integer
      Public TimerReference As System.Threading.Timer
      Public TimerCanceled As Boolean
End Class

Sub RunTimer()
   Dim StateObj As New StateObjClass()
   StateObj.TimerCanceled = False
   StateObj.SomeValue = 1
   Dim TimerDelegate As New Threading.TimerCallback(AddressOf TimerTask)
   ' Create a timer that calls a procedure every 2 seconds.
   ' Note: There is no Start method; the timer starts running as soon as 
   ' the instance is created.
   Dim TimerItem As New System.Threading.Timer(TimerDelegate, StateObj, _
                                               2000, 2000)
   StateObj.TimerReference = TimerItem  ' Save a reference for Dispose.

   While StateObj.SomeValue < 10 ' Run for ten loops.
      System.Threading.Thread.Sleep(1000)  ' Wait one second.
   End While

   StateObj.TimerCanceled = True  ' Request Dispose of the timer object.
End Sub

Sub TimerTask(ByVal StateObj As Object)
   Dim State As StateObjClass = CType(StateObj, StateObjClass)
   Dim x As Integer
   ' Use the interlocked class to increment the counter variable.
   System.Threading.Interlocked.Increment(State.SomeValue)
   Debug.WriteLine("Launched new thread  " & Now)
   If State.TimerCanceled Then    ' Dispose Requested.
      State.TimerReference.Dispose()
      Debug.WriteLine("Done  " & Now)
   End If
End Sub

Thread timers are particularly useful when the System.Windows.Forms.Timer class is unavailable, such as when you are developing console applications.

Canceling Tasks

One advantage of multithreading is that the user interface part of an application continues to be responsive, even when tasks are being performed on other threads. Synchronization events and fields that act as flags are often used to notify another thread that you want it to stop. The following example uses synchronization events to cancel a task. To use this example, add the following module to a project. To start a thread, call the StartCancel.StartTask() method. To cancel one or more running threads, call the StartCancel.CancelTask() method.

Module StartCancel
   Public CancelThread As New System.Threading.ManualResetEvent(False)
   Public ThreadisCanceled As New System.Threading.ManualResetEvent(False)
   Private Sub SomeLongTask()
      Dim LoopCount As Integer
      Dim Loops As Integer = 10
      ' Run code in a While loop until 10 seconds passes, or
      ' until CancelThread is set.
      While Not CancelThread.WaitOne(0, False) And LoopCount < Loops
         ' Do some kind of task here.
         System.Threading.Thread.Sleep(1000) ' Sleep for one second.
         LoopCount += 1
      End While
      If CancelThread.WaitOne(0, False) Then
         'Acknowledge that the ManualResetEvent CancelThread is set.
         ThreadisCanceled.Set()
         MsgBox("Canceling thread")
      Else
         MsgBox("Thread is done")
      End If
   End Sub

   Public Sub StartTask()
      ' Starts a new thread.
      Dim th As New System.Threading.Thread(AddressOf SomeLongTask)
      CancelThread.Reset()
      ThreadisCanceled.Reset()
      th.Start()
      MsgBox("Thread Started")
   End Sub

   Public Sub CancelTask()
      ' Stops any threads started by the StartTask procedure.
      ' Notice that this thread both receives and sends 
      ' synchronization events to coordiante the threads. 
      CancelThread.Set()  ' Set CancelThread to ask the thread to stop.
      If ThreadisCanceled.WaitOne(4000, False) Then
         ' Wait up to 4 seconds for the thread to 
         ' acknowledge that it has stopped.
         MsgBox("The thread has stopped.")
      Else
         MsgBox("The thread could not be stopped.")
      End If
   End Sub
End Module

Conclusions

Multithreaded processing is the key to scalable, responsive applications. Visual Basic .NET supports a robust, multithreading development model that enables developers to quickly harness the power of multithreaded applications.

  • Visual Basic .NET uses the new .NET Framework classes that make it easy to create multithreaded applications.
  • Remember that although multiple threads can improve performance, each thread has an associated cost in terms of additional memory required to create the thread and processor time required to keep it running.
  • Thread properties and methods control the interaction among threads and determine when resources are available to running threads.
  • Although multithreading may seem to introduce chaos, you can control running threads by using synchronization techniques.
  • Multithreading produces scalable applications by allocating available resources efficiently even as application complexity increases.

Using the techniques discussed in this article you can develop professional applications that can handle even the most processor-intensive tasks.

Additional Resources

Guidelines for Asynchronous Programming
Explains the concept of asynchronous programming and its support in the .NET Framework.
Delegates, Events, and Remoting
Explains the concepts of delegates and remoting and how .NET remoting supports asynchronous programming.
Asynchronous Programming Design Pattern
Demonstrates asynchronous programming practices by example.
Remoting Example: Asynchronous Remoting
Includes a sample that demonstrates asynchronous remoting.
Show:
© 2014 Microsoft