Understanding Threads using Visual Basic 2005 - Part 1
 
Published: 02 Jun 2008
Abstract
In this first part of the series, Abhishek describes the concepts of threading, different procedures to create threads, synchronization among multiple threads, uses of Sleep and Join methods, code locking using SyncLock, and Monitor with sample code in Visual Basic 2005.
by Abhishek Kumar Singh
Feedback
Average Rating: 
Views (Total / Last 10 Days): 51990/ 66

Introduction

In terms of kernel scheduling, a process is a heavy unit, whereas a thread is the smallest and lightest unit. Each process reserves its own memory space (i.e. address space) as well as resources. Thread does not specifically own resources except registers and stack.  Each thread has to be a part of at least one process. All threads which are part of single process share the same address space. Overall it means that if we divide a heavy process into multiple threads to execute concurrently (so called multithreading), we can get the result much faster. Thinking like this feels good, but the major responsibility on programmers, which must be known, understood, and applied in multithreaded programs is, thread synchronization, thread communication methodology, safe resource sharing among threads etc. In the following article you will see about these one by one with lots of Visual Basic sample code (with output details) written for better understanding. I have used Visual Basic 2005 for the code examples in this article.

Important Terms to Know About the Threading Model

·         Thread- The smallest unit of program or part of a process, running in the execution area. Several threads can be the part of a single process.

·         Process- An instance of a program. Any single process contains at least one thread.

·         Context switching- A methodology used by the operating system to switch the processor among threads to perform parallel execution by giving time slices to each of them. Context switching between processes is generally slower than context switching between threads.

·         Kernel- Core of operating system, which performs thread instantiation and execution. It provides several system call interface for programmers to deal with threads to customize the execution approach.

·         Multitasking (with multi-core CPU)- A method of operating system in which it shares CPU time among multiple tasks (processes) by switching between them on the basis of some predefined process scheduling algorithms. As we all know, in single CPU machine, in truth, no two processes can run at the same point of time. Multitasking gives a impersonate way to it. But now multi-core CPUs are available in the market by which more than one process can run concurrently depending upon the number of CPU cores. Even single process is divided into sub processes and processed by different CPU cores. In this way multi-core CPUs give better performance.

·         Multithreading- An execution model which allows several threads to execute independently under the context of a single process. Multiple threads can run simultaneously, so called concurrent execution. Threads can share process resources. So it gives faster execution model. In other words we can say that multithreading is "multitasking applied on processes."

Different ways to create threads in .NET

Using System.Threading namespace

Listing 1 – Create threads using System.Threading

Sub Main()
  Dim oThread As New System.Threading.Thread(New System.Threading.ThreadStart( _
                                             AddressOf MyThreadJob))
  oThread.Start()
  Console.Read()
End Sub
Public Sub MyThreadJob()
  Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & _
    " This is MyThreadJob")
End Sub
Public Sub MyThreadJob()
  Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString())
End Sub

In this case the thread method can be either sub or function.

Using System.Windows.Forms.Timer namespace

We can use Windows.Forms.Timer object to create threads in each specified interval. Timer object accepts time interval value in milliseconds. Warning: Windows.Forms.Timer object may or may not work in applications which are not windows based. For example console application. Even if you add a reference to this namespace and your code compiles successfully, it may fail to create threads during execution. So we should avoid using Windows.Forms.Timer in non-form based applications. Use System.Threading.Thread instead.

Here is the sample code for a windows application to create threads using System.Windows.Forms.Timer

Listing 2 – Creating thread using System.Windows.Forms.Timer.

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles MyBase.Load
        Dim oTimer As New System.Windows.Forms.Timer()
        AddHandler oTimer.Tick, AddressOf MyThreadJob
        oTimer.Interval = 5000
        Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & "
        Starting Thread")
        oTimer.Start()
      
End Sub
 
Private Sub MyThreadJob(ByVal o As ObjectByVal e As EventArgs)
  MsgBox(Now.ToString() & " " & Now.TimeOfDay.ToString() & " This is MyThreadJob")
End Sub

Notice the signature of the thread method. In case of Windows.Forms.Timer the thread method must have Object and EventArgs types in its signature.

Using System.Threading.Timer namespace

Listing 3 – Creating thread using System.Threading.Timer

Sub Main()
  Dim oTimer As New System.Threading.Timer(New System.Threading.TimerCallback( _
    AddressOf MyThreadJob), Nothing, 0, 5000)
  Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & _
    "  System.Threading.Timer configured")
 
  Console.Read()
End Sub
 
Private Sub MyThreadJob(ByVal s As Object)
  Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & _
    " This is MyThreadJob")
End Sub

Notice the signature of the thread method. In case of System.Threading.Timer the thread method must have parameter of type Object. It is normally used to share state information between threads.

Using System.Timers.Timer

Listing 4 – Creating thread using System.Timers.Timer

Sub Main()
  Dim oTimer As New System.Timers.Timer(5000)
  AddHandler oTimer.Elapsed, AddressOf MyThreadJob
  oTimer.Enabled = True
  Console.Read()
End Sub
 
Private Sub MyThreadJob(ByVal s As ObjectByVal e As System.Timers.ElapsedEventArgs)
  Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & _
    " This is MyThreadJob")
End Sub

Again, Notice the signature of the thread method MyThreadJob(). It must have Object and Timers.ElapsedEventArgs types in the signature.

Among four methodologies mentioned above, which we can use to build multithreaded applications, I would recommend going for the first procedure (for using System.Threading.Thread) always, unless we don't have any specific need of using others. Why? You can find differences among these in the real world through MSDN and forums.

At this moment you should know how to create threads in application. Each thread runs independently. Now your next task should be to control these independent threads in such a manner that they all can run as per our wish.

Thread Synchronization (A must do thing)

Let's think your application (process) is a project manager and each thread of it as a project team member. You surely want your team members to work together, not independently, right? For this you would want them to instruct and manage based on guidelines, work approach, periodic status reporting, team communication, proper resource utilization, conflict prevention, etc. You would want to have total control over the work environment to get the final target to accomplish. So here you need to have synchronization among team members. In the same way, we need synchronization among threads.

Important constructs which we can use for Synchronization

Some important objects which we can use for Synchronization are given below. We will see uses of all these in later part of the article.

1.    To block current thread

·         Sleep() - This is used very commonly to block thread for some specified time interval (in milliseconds)

·         Join() - To block current thread and instantiate another thread. When new thread finishes, it will resume its processing.

2.    To implement code locking mechanism among threads

·         SyncLock - To lock some part of code to be run by only one thread at a time. SyncLock term is used in VB.Net. Its C# equivalent is Lock.

·         Monitor - Similar to mutex in the functionality but different coding approach with extra implementation features.

·         Mutex - To lock some part of code to be run by only one thread at a time with cross-process accessibility feature. Mutex is the extension of windows kernel objects.

·         Semaphore - To lock some part of code to be run by only one thread at a time with cross-process accessibility feature, with additional feature to prevent deadlock in better way. Semaphore is the extension of kernel objects.

·         Synchronization Context - To lock some part of code to be run by only one thread at a time just by using class attribute [Synchronization] and inheriting ContextBoundObject class.

·         ReaderWriteLock - To allow specific number of threads to run particular set of code simultaneously with some access privilege set among them.

3.    To implement event-driven thread synchronization in the application

·         EventWaitHandle - A class which provides some methods and properties to manage threads by sending/receiving event signal among each other. This also uses windows kernel object internally to perform the task. Two important class which we use are:

·         1. AutoResetEvent

·         2. ManualResetEvent

I will describe the implementation procedures of each one right from here. It will continue in my next few articles as well.

Implementing Sleep() and Join() methods in Threading

Create a console application and update the code in module as given below.

Listing 5 – Implementing thread creation without using Sleep and Join methods

Module Module1
 
Sub Main()
  Console.WriteLine("{0} : {1} : Main thread started...", _
    Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
 
  Dim oThOneMethod As Threading.ThreadStart = _
    New Threading.ThreadStart(AddressOf DoAction)
  Dim oThTwoMethod As Threading.ThreadStart = _
    New Threading.ThreadStart(AddressOf DoAction)
 
  Dim oTh1 As Threading.Thread = New Threading.Thread(oThOneMethod)
  Dim oTh2 As Threading.Thread = New Threading.Thread(oThTwoMethod)
 
  oTh1.Start()
  oTh2.Start()
 
  Console.WriteLine("{0} : {1} : Main thread finishing...", _
                    Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End Sub
 
Public Sub DoAction()
  Console.WriteLine("{0} : {1} : To sleep now...", _
    Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
  Threading.Thread.Sleep(5000)
  Console.WriteLine("{0} : {1} : is woke up...", _
    Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End Sub
End Module

** [Please note that you should use CTRL+F5 key combination to run the application with code provided in this article since I have not used Console.Read() at the end of Main(). CTRL+F5 will cause the console to wait for any key press at the end. This way you can see the output in the console window. Otherwise you can add Console.Read() in main().]

If you run this application (CTRL+F5), you will get output in console window a given below.

Figure 1 – Console output of application

Does the result seem proper? No, because in general we don’t want to stop the main thread before finishing generated threads. Here is what we need to use Join() method which will wait till new threads finish. Modify the Main() method to use Join() as given below. I have only added oTh1.Join() and oTh2.Join() in the Main() method.

Listing 6 – Implementing thread creation using Sleep and Join methods

Sub Main()
  Console.WriteLine("{0} : {1} : Main thread started...", _
    Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
 
  Dim oThOneMethod As Threading.ThreadStart = _
    New Threading.ThreadStart(AddressOf DoAction)
  Dim oThTwoMethod As Threading.ThreadStart = _
    New Threading.ThreadStart(AddressOf DoAction)
 
  Dim oTh1 As Threading.Thread = New Threading.Thread(oThOneMethod)
  Dim oTh2 As Threading.Thread = New Threading.Thread(oThTwoMethod)
 
  oTh1.Start()
  oTh2.Start()
 
  oTh1.Join()
  oTh2.Join()
 
  Console.WriteLine("{0} : {1} : Main thread finishing...", _
                    Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End Sub

Now run the application (CTRL+F5), you should see following console output.

Figure 2 – Console output of the application

This time result is as we wanted. New threads are finished before the main thread.

Implementing thread lock using SyncLock

This time we will move delegate method DoAction to a separate class for better understanding. We will modify the code inside DoAction to use lock features. Let’s add a class CThread in the application and add the DoAction method in it. In this example to use SyncLock in it what we are going to do is just to put all lines of code in DoAction method to be placed between SyncLock Me and End SyncLock constructs. The complement class code is given below.

Listing 7 – Implementing SyncLock (locking feature) in thread delegate method

Public Class CThread
  Public Sub DoAction()
    SyncLock Me
    Console.WriteLine("{0} : {1} : To sleep now...", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
    Threading.Thread.Sleep(5000)
    Console.WriteLine("{0} : {1} : is woke up...", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
    End SyncLock
  End Sub
End Class

So we also need minor modification in the Main(). We just need to create object of class CThread in Main() and set thread start delegate method DoAction with class object. The code of Main() is given below.

Listing 8 – Code for Main method of the application

Sub Main()
 
  Dim oCThread As New CThread()
 
  Console.WriteLine("{0} : {1} : Main thread started...", _
    Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
 
  Dim oThOneMethod As Threading.ThreadStart = _
    New Threading.ThreadStart(AddressOf oCThread.DoAction)
  Dim oThTwoMethod As Threading.ThreadStart = _
    New Threading.ThreadStart(AddressOf oCThread.DoAction)
 
  Dim oTh1 As Threading.Thread = New Threading.Thread(oThOneMethod)
  Dim oTh2 As Threading.Thread = New Threading.Thread(oThTwoMethod)
 
  oTh1.Start()
  oTh2.Start()
 
  oTh1.Join()
  oTh2.Join()
 
  Console.WriteLine("{0} : {1} : Main thread finishing...", _
    Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End Sub

Run the application (CTRL+F5). You should see output window like:

Figure 3 – Console output of the application with SyncLock uses

If you look the output carefully you can see that even if thread 3 has gone to sleep for 5 seconds, thread 4 could not enter the SyncLock section. In fact thread 4 just waits at the beginning of SyncLock till previous thread (# 3 here) leaves the SyncLock section. When thread 3 woke up and comes out of SyncLock section, thread 4 resumes its execution. In the same way, if there would have been more thread like # 5,6,7 etc, all would have gone into a thread queue and processed one by one with the rule that only one thread can enter into the SyncLock at a time.

Implementing thread lock using Monitor

We have another useful class in .net- Monitor. The Monitor class provides functions through which we can implement locking mechanism in the threads. In general it works similar to the SyncLock but the advantage is that we can do lock based error handling with Monitor. For this we can use Enter() and Exit() functions with our usual error handling Try…Catch…Finally block. Let's modify the DoAction method to use Monitor as given in the class below.

To understand its implementation, just modify the existing DoAction method to use Monitor class as given below. No need to make any change in Main() for now.

Listing 9 – Thread delegate method using Monitor class

Public Class CThread
  Public Sub DoAction()
    Try
    Threading.Monitor.Enter(Me)
    Console.WriteLine("{0} : {1} : To sleep now...", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
    Threading.Thread.Sleep(5000)
    Console.WriteLine("{0} : {1} : is woke up...", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
    Catch ex As Exception
    Console.WriteLine("{0} : {1} : Error in monitor section,exiting monitor...", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
    ' do some job like error logging in file or database..
    Finally
    Threading.Monitor.Exit(Me)
    End Try
  End Sub
End Class

We can code Monitor.Enter and Monitor.Exit methods anywhere inside the Try…Catch…Finally block of the delegate method. In this way it gives more flexibility to implement and handle complex logic inside lock area.

Run the application (CTRL+F5), you would see console window as:

Figure 4 – Console output window of Monitor implementation

As this result same as the SyncLock example we had done before. To test how Monitor is useful with Try…Catch…Finally block, we will modify the DoAction method to read a file (say c:\abc.txt) which does not exist in the computer. The very first instance of the delegate method will be the victim of FileNotFound error. In catch block we will create that file so that next all other threads will not get that error. Here is complete code of class containing DoAction method.

Listing 10 – Thread delegate method using Monitor class and Try…Catch…Finally to handle runtime errors.

Public Class CThread
  Public Sub DoAction()
    Try
    Threading.Monitor.Enter(Me)
    Console.WriteLine("{0} : {1} : To sleep now...", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
    Threading.Thread.Sleep(5000)
    IO.File.ReadAllText("c:\abc.txt")
    Console.WriteLine("{0} : {1} : is woke up...", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
 
    Catch ioErr As IO.FileNotFoundException
    Console.WriteLine("{0} : {1} : Error- File Not Found.", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
    ' create the file for next all threads
    IO.File.Create("c:\abc.txt")
    Catch ex As Exception
    Console.WriteLine("{0} : {1} : Error in monitor section : {2}", _
      Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString(), ex.ToString())
    ' do some job like error logging in file or database..
    Finally
    Threading.Monitor.Exit(Me)
    GC.Collect()
    End Try
  End Sub
End Class

One more thing you may have noticed that I have used GC.Collect() at the end in finally block. I used it to release any resources (a file in this example) being used by thread, so that other thread would not get any file access error.

Run the application (CTRL+F5), you would see the console output window as:

Figure 5 – Console output of the application showing error handling with Monitor uses.

You can see in the result that managed thread # 3 got a FileNotFound error and next thread # 4 executed normally since thread # 3 created the missing file in the Catch block.

Few sample project of this article can be downloaded though the download links given below.

Downloads and Summary

[Download LockExample]

[Download MonitorExample]

Summary

So, that's it for this article. I will cover other very interesting and useful threading models like Mutex, Semaphore etc in the next part of this article. I hope this article would be helpful in understanding threading concepts and implementation. Your suggestions and comments are welcome. Thank you for giving time on ASPAlliance.



User Comments

Title: Thread   
Name: Pari
Date: 2012-10-16 4:02:22 AM
Comment:
very helpful
Title: Enginer   
Name: Didier Fonseca
Date: 2010-11-10 4:46:13 PM
Comment:
Thanks,very good and easy to understand tutorial
Title: dig deeper   
Name: rs
Date: 2008-06-09 10:47:16 AM
Comment:
nice introduction in synchronization. next thing that comes up comparing different solutions is rating them. how do they differ (e.g. in performance) and which is suitable for what kind of problem ...
Title: thanks   
Name: Abhishek Singh
Date: 2008-06-06 6:50:45 AM
Comment:
thanks to all of you!
Title: Vey Good Article   
Name: Babita
Date: 2008-06-05 3:23:22 AM
Comment:
Very useful article about thread.
Title: Good one   
Name: Soumya
Date: 2008-06-03 2:52:33 AM
Comment:
Very helpful definitions and examples.
Title: nice article   
Name: rupesh
Date: 2008-06-03 2:40:32 AM
Comment:
Very nice article about thread

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-19 6:02:51 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search