• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 486
  • Last Modified:

How Threads work in VB.NET

Hi - I am trying to implement some logic using threads and for this reason I am trying to understand how threads work in VB. I have created a sample project to illustrate the scenario I am trying to do and it would be very good if someone can please advise regarding my questions below.

(The code is based on the information found on this url:http://www.devcity.net/Articles/190/2/article.aspx)

Question 1 -
Will the code in Button1_Click generate three totally seperate independent threads?
(The reason I ask is because if I had a method generating a random number in DoWork the same number was generated..?) Maybe I should define a new worker object on each loop run?

Question 2-
When the DoWork sub completes will the thread stop or should I in some way explicitly release the memory? (Maybe by calling StopWork)

Question 3-
What exactly does the code in  StopWork do? I have tried to understand it but I can't really get my head around it.

Thanks J



Public Class Form1
    Private worker As New Worker()
 
 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
 
        Dim x As Integer = 5
 
        For i As Integer = 0 To x
            Dim wt As System.Threading.Thread
            Dim ts As System.Threading.ThreadStart
            ts = AddressOf worker.DoWork
            wt = New System.Threading.Thread(ts)
            wt.Start()
        Next
    End Sub
 
End Class
 
'------------------------------------------------------------------------------------------------------------------
 
 
Public Class Worker
    Private m_thMain As System.Threading.Thread
    Private m_booMustStop As Boolean = False
    Private m_rndGen As New Random(Now.Millisecond)
 
 
    Public Sub StopWork()
 
        If Not m_thMain Is Nothing Then
            If Not m_thMain.Join(100) Then
                m_thMain.Abort()
            End If
        End If
 
    End Sub
 
    Public Sub DoWork()
        m_thMain = System.Threading.Thread.CurrentThread
        Dim i As Integer = m_rndGen.Next
        m_thMain.Name = "Thread" & i.ToString
 
        Debug.WriteLine("do some work")
 
    End Sub
 
 
End Class

Open in new window

0
jes12345
Asked:
jes12345
  • 7
  • 6
  • 2
  • +1
9 Solutions
 
oobaylyCommented:
1).
What you're doing here is executing DoWork three times, but on the same object which is going to cause a lot off issues. Each thread is using the same m_thMain variable and renaming it:
Thread1 -> sets m_thMain & Calls it "Thread1"
Thread2 -> sets m_thMain & Calls it "Thread2"
Thread3 -> sets m_thMain & Calls it "Thread3"
Thread1 -> accesses  m_thMain & finds it is called Thread3

2).
When DoWork finishes, you don't have to do anything as everything is managed.

3).
What you're doing in StopWork is blocking the calling thread for 100ms, waiting for the m_thMain (which could be either, Thread1, Thread2 or Thread3) to finish. If m_thMain is still running after 100ms, it is aborted.
It's not doing anything really. Thread.Join would be used to create a rudimentary timeout for an asynchronous method. Have a look at my example

What you really want to be using is the BackgroundWorker, as that's what it looks like you're trying to create:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

public sub Main(args as string())
  Thread td = new Thread(addressof Foo)
  td.Start()
 
  // Only allow Foo to run for 2s, otherwise we want it to end
  if (not td.Join(2000)) then
    td.Abort()
  end if
end sub
 
 
public sub Foo()
  try
    ' Do something than can take a varying amount of time
  catch (exAbort as ThreadAbortException)
    ' Thread was aborted (in this case because
    ' we have decided it has timed out)
  catch (ex as Exception)
    ' Some other error has occured
  finally
    ' Tidy up stuff like open files
  end try
end sub

Open in new window

0
 
käµfm³d 👽Commented:
Question 1: Yes, it creates separate threads--in your case 5.

Question 2: Yes, the thread stops. It is released since it's object is out of scope when the function that created it ended. This does not mean your threads are dead when the function ends.

Question 3: When a thread enters the DoWork() function, it sets itself as the objects main thread. StopWork() checks the variable holding the reference to whichever thread set itself and checks whether or not it is null--if it is null, no thread set itself. If it is not null, then the thread executing the StopWork() function blocks (or waits) until the thread referenced by m_thMain is finished or 100 milliseconds, whichever comes first.
0
 
oobaylyCommented:
Clarification:
Don't know why I said three times, rather than 5 (probably because I was thinking of 3 questions)
The reason I said in 2 that "It's not doing anything really." is because in your current example you can't be sure which thread m_thMain is.
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
DarrenDCommented:
Hi,

On another note here is a really good article on threading. It's in C# but it's worth a read.

http://www.albahari.com/threading/default.aspx#_Introduction

Darren
0
 
jes12345Author Commented:
Hi oobayly and kaufmed - thanks for your advises!
It seems to me that your replies differ somewhat but I have a better understanding now.

Regarding Question 1) I think your reply oobayly makes most sense because I had strange issues as you pointed out could happen.

Regarding Question 2) I now know the threads are stopped and managed. Kaufmed when you say "This does not mean your threads are dead" can I please ask you to confirm that you mean the threads are stopped and released but may not be dead since they may be waiting for garbage collection?

Regarding Question 3)
So I understand it correctly that join in your example will halt the method Main for 2 seconds? I guess it is the word join that I don't quite understand..

Finally I guess BackgroundWorker would be a good option but the issue is that I do not know on beforehand how many threads I need and therefore I need to generate them on the fly. I believe backgroundworker have to be defined as a member variable using the withevents keyword? (I might very well be wrong on this...) I have created a sample but I get some strange behaviour.. Try to comment out the msgbox and the behaviour is more like expected..

Also are there any benefits using a BackroundWorker rather than manually creating a thread?

Thanks again J
Public Class Form1
    Dim WithEvents bw As System.ComponentModel.BackgroundWorker
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 
        For i = 0 To 5
            Debug.WriteLine(i)
            bw = New System.ComponentModel.BackgroundWorker
            bw.RunWorkerAsync()
        Next
 
        
    End Sub
 
    Private Sub bw_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bw.DoWork
 
        MsgBox("a")
        Debug.WriteLine("kjører ha")
 
    End Sub
 
End Class

Open in new window

0
 
jes12345Author Commented:
DarrenD - Sorry but I did not see your update before my last reply. Many thanks for the article - I will definately read this. Thanks J
0
 
oobaylyCommented:
Regarding Join, what it does is:
Block the thread that called it (in my example, the app thread) in for the given amount of time. If it returns true, you know that the thread has completed. If it returns false you know the the thread is still running. As it's still running, You've the option to let it continue or as in my example, kill it.

DarrenD's article looks pretty good as it'll explain locking, deadlocks etc far better than I can.
0
 
käµfm³d 👽Commented:
For a clarification to oobayly's post, there is no guarantee that

Thread1 -> sets m_thMain & Calls it "Thread1"
Thread2 -> sets m_thMain & Calls it "Thread2"
Thread3 -> sets m_thMain & Calls it "Thread3"
Thread1 -> accesses  m_thMain & finds it is called Thread3

will happen in that order. The thread scheduler takes care of sequencing thread execution. You may run your application 100 times and get the same order/results. On the 101st time, you may get something different. If you are looking to achieve order with your threads, then you will need synchronization.

As to clarifying what I said, in Button1_Click you are creating and running threads. You are creating Thread objects to accomplish this. Eventually, Button1_Click will go out of scope, as will your Thread objects. What I mean is that the Thread object you created will be gone, but the thread will continue to run until finished or aborted.

In the example below, I create and start a thread in Form_Load. Form_Load will finish execution and the form is shown. At this point, the Thread object "th" is out of scope and marked for GC. In the background, the thread that was started using "th" is still running. After the MessageBox is closed, that thread is no more.
Public Class Form1
 
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim th As New Threading.Thread(AddressOf Work)
 
        th.Start()
    End Sub
 
    Private Sub Work()
        Dim i As Integer = 0
 
        For i = 0 To Integer.MaxValue - 2
            i += 1
        Next
 
        MessageBox.Show("Thread done")
    End Sub
 
End Class

Open in new window

0
 
oobaylyCommented:
@kaufmed
I agree completely with that. Wasn't sure whether I should have mentioned it.

Ah, multithreading: The solution to (and cause of) all of a programmer's problems.
(with apologies to The Simpsons)
0
 
jes12345Author Commented:
Brilliant - many thanks for the explanations and clarifications. I guess my final question is then weather I will benefit from using a backroundworker? Did you have a chance to look at my example above? Is there a way to start a backroundworker without the use of events? (I.e that I can define the bw inside the for loop without WithEvents and start it from here)

Thanks J
0
 
oobaylyCommented:
If you have a thread that just has to run in the background, rather than report it's progress & provide support for cancellation, using a Thread is probably easier (and possibly) more lightweight.

If you want to report back to the caller (ie. Update the UI) and provide support for cancellation, use the BackgroundWorker. This is how you can use BackgroundWorker without having to create it as a member variable.

With either option, you should take care accessing objects from multiple threads.
Private Sub StartWorker()
    Dim worker As New BackgroundWorker()
    AddHandler worker.DoWork, AddressOf worker_DoWork
    AddHandler worker.ProgressChanged, AddressOf worker_ProgressChanged
    AddHandler worker.RunWorkerCompleted, AddressOf worker_RunWorkerCompleted
    worker.RunWorkerAsync("An object to pass")
End Sub
 
Private Shared Sub worker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
    Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)
End Sub
 
Private Shared Sub worker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
    Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)
End Sub
 
Private Shared Sub worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
    Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)
    Dim passedObject As Object = e.Argument
End Sub

Open in new window

0
 
jes12345Author Commented:
Thanks for the example. I still do not quite understand what is going on.. Would you mind giving me a brief explanation? Especially the lines:
Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)
What is this line doing?

Also I guess the argument is optional?I.e I could have the following:

worker.RunWorkerAsync()
and then
Private Shared Sub worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
    Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)
End Sub
0
 
oobaylyCommented:
"Also I guess the argument is optional?I.e I could have the following:", exactly. I just provided the example so you knew for the future that you can pass something to DoWork

The reason I put the "Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)" line in is this:
As the BackgroundWorker isn't a member variable, you have to access by the sender parameter. TryCase is a bit like CType, except it returns a null reference (Nothing) if it can't be cast. I just used a tool to convert my code to VB from C#

For example, if you wanted to raise the ProgressChanged event, you would call worker.ReportProgress() from the DoWork event. Then, in the ProgressChanged event handler, you could update the UI (ie. a progressbar) so the user knows something is still happening.

0
 
jes12345Author Commented:
Ok got it now- Many thanks for explaining.
(That translation tool sounds handy - is it expensive?)

Thanks again guys for all your advises- I really have a better understanding of threading now:) J
0
 
oobaylyCommented:
0
 
jes12345Author Commented:
Fantastic!:) Again thanks for all help! J
0

Featured Post

[Webinar] Cloud and Mobile-First Strategy

Maybe you’ve fully adopted the cloud since the beginning. Or maybe you started with on-prem resources but are pursuing a “cloud and mobile first” strategy. Getting to that end state has its challenges. Discover how to build out a 100% cloud and mobile IT strategy in this webinar.

  • 7
  • 6
  • 2
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now