Some time ago I was inspired to write an article here about Lambdas and Delegates after observing a couple of posts where members voiced their misunderstanding of them. It seemed quite helpful to a couple of people and now I'm inspired yet a again to write about another troubling area in the .Net world, multi-threading. In this article I hope to be able to take someone who's only heard about it from the depths ignorance about it to being able to competently command their processor's cores to do what they were made to do, share the burden of executing one or more tasks simultaneously.
Lets Begin
Background
To understand what multi-threading is you must first understand what happens when you run an executable program on an operating system. The OS loads the executable program into memory and the processor executes its code line by line. However with this simple logic one will realize that eventually the lines will run out and the program will end but when you open NotePad, it doesn't end until you tell it to so what gives ? This is because a program is basically one big loop where the same instructions keep getting executed over and over. The reality is actually far more complex that this but its the underlying idea. When you run NotePad, the program enters one big loop and which basically keeps checking for weather the user typed something or clicked a menu option and then it responds to these inputs by executing other code before returning to the loop. So if you press the letter 'K', the code leaves the loop and executes the code that is responsible to rendering the letter 'K' into NotePad's window. When this code is finished, it returns to the loop where it left off, waiting to process other messages.
Think of the code looking like this:-
vbnet Code:
'
Public Sub Main()
Do
Dim msg As MESSAGE = GetMessage()
If msg IsNot Nothing Then
ProcessMessage(msg)
End If
Loop
End Sub
The loop repeatedly checks for messages(key presses, mouse clicks etc) and when it finds one, it passes it to a procedure that knows how to take action. Its an intriguing design but what happens the action taken by ProcessMessage leads to another loop, one which will take several seconds or even minutes ? Eg. You click a hyperlink in Internet Explorer to download a file.
Imagine that this is the code that Internet Explorer calls to download a file:-
vbnet Code:
'
Public Sub DownloadFile(ByVal url As String)
Dim fileSize As Integer = GetFileSize(url)
Dim fileName As String = GetFileName(url)
Dim curPos As Integer = 0
Do While curPos < fileSize
Dim fileData() As Byte
Dim bytesRecd As Integer = GetNextChunk(fileData)
curPos += bytesRecd
appendToFile(fileName, fileData)
Loop
End Sub
Now thats not the actual code but I'd expect the concept to be similar to that, so just imagine that it is. What would happen ? Well notice that DownloadFile has a loop which repeatedly gets chucks of the file, so if this file is several gigabytes in size then this loop is going to be executing for quite a while. If clicking the download hyperlink led to that sub being executed by ProcessMessage then common sense would dictate that Internet Explorer would not be able to process any more messages until DownloadFile has finished executing and returned. This means that Internet Explorer would not respond to anything you do because the main message loop is halted. Visualize it like this:-
vbnet Code:
'
Public Sub Main()
Do
Dim msg As MESSAGE = GetMessage()
If msg IsNot Nothing Then
If msg.MsgType = FILEDOWNLOAD Then
'imagine msg.Data has the url
DownloadFile(msg.Data)
End If
End If
Loop
End Sub
Now I must re-iterate that this is not the actual code that does these things in Windows and IE. Its just to give you an idea of what happens.
When DownloadFile is called the message loop cannot process additional messages until DownloadFile returns. However, we all know that we can download a file, even many files and still browse the internet. How can this be ? This is where multi-threading comes into play.
What is multi-threading
While many may take the fact that you can still browse while you have a file downloading in the background for granted, it is in fact multi-threading that gives you this luxury. When you have downloads in progress in Internet Explorer, these downloads are being handled by their own threads and the main browser window is running on a different thread so that each of these tasks can proceed independently. Going back to my earlier illustrations, what happens is that DownloadFile is passed off to another thread and returns immediately so the message loop can proceed as normal. Threads are exactly that, threads of execution. So what we have are two loops running at the same time. The reality though is that they are not running at the same time but Windows is actually switching between then very very quickly to give the impression of concurrency. The operating system may execute two lines of code in the message loop and switch to the download loop and execute two lines before switching back or switching to yet another thread doing something else. Note though, that on multi-core systems these two threads can actually be executing at the same time. This is what multi-threading is about, execution separate sections of code seemingly or actually at the same time. So now we come to the fun part, actually doing it which we will begin in the next post.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
We'll start off simple and then build on it. Let us begin with a simple operation. We are going to use a loop that counts from 1 to 100 to simulate a long running operation like a file download.
Open a new Windows Forms project and place a Button and a Label on Form1. Double click the Button to bring up its Click event handler then code the event handler as:-
vbnet Code:
'
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
For i = 1 To 100
Label1.Text = CStr(i)
Threading.Thread.Sleep(200)
Next
End Sub
Execute the program and click the button. You should notice that the UI freezes until the count is actually complete. Notice that while its frozen, the label is not being updated. Why is this ? Well remember the message loop I mentioned in the Backround section of this article ?. Pressing the button executes the count loop so while this loop is running the message loop of the UI cannot execute and process the repaint messages for the label to update itself. The UI also cannot process mouse clicks, and key presses. You cannot even move the window.
Our solution is to pass off the count to another thread to execute so that the UI's message loop remains unhindered. Remember, the UI's message loop is running in its own thread usually called the main thread or the UI thread.
We'll start off by putting our counting operation into a separate sup:-
vbnet Code:
'
Private Sub Count(ByVal Max As Integer)
For i = 1 To Max
Label1.Text = CStr(i)
Threading.Thread.Sleep(200)
Next
End Sub
What we do next is really a matter of preference. Some people elect to use the BackgroundWorker component to do their multi-threading. I'm not going to show that approach. The forum is rich with examples on using the BackgroundWorker and I've always preferred to do my threading directly. This is the direction I'm going to demonstrate.
Now that we have our count in its own sub. Code the Button's Click event as:-
vbnet Code:
'
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t1 As New Threading.Thread(AddressOf Count)
t1.Start(100)
End Sub
The first line instantiates a new Thread object and passes it the address of the Count sub since this is the sub we want to execute on another thread. Its very important to note that here lies a very serious limitation. Only a sub with a single parameter is allowed. Luckily in our example Count only has one parameter, the number at which the count should end.
The second line starts the thread. Start takes one parameter which it uses to call the sub hence:-
vbnet Code:
t1.Start(100)
Is like:-
vbnet Code:
Count(100)
Only it runs on a different thread from the thread it was called from.
If you have Option Strict On the add this Count overload:-
vbnet Code:
'
Private Sub Count(ByVal Max As Object)
If TypeOf Max Is Integer Then
Count(CInt(Max))
End If
End Sub
The reason is because the Thread class's constructor expects the address to a sub that takes one parameter of type object. Option Strict would not allow the address of the Count that takes an Integer as its parameter to be used. Now execute the program.
At this point you should be ready to pull out your hair because you would get this error:-
So just what in God's name is happening here ? Don't worry, that error is actually happening to prevent potential disasters. Remember I said that the UI has its own thread ? Well think about it what is the UI ? Its basically every thing to do with what you see. Labels, pictures, text are all UI elements so any control that is responsible for showing these things is a part of the UI. The windows message loop runs in the UI thread and its responsible for dealing with these controls. Its repainting, its responses to key presses and mouse clicks are all the UI's thread is responsible for. Its rather intrusive for another thread to just come out of nowhere and make a change to a control that its not responsible for. How does this foreign thread know what the UI is doing with any give control at any given moment ? It doesn't and if it interferes with a control while the UI was in the middle of making some change to the control then it can really cause some instability in the application if the control ends up in some invalid state. This is why VB.Net prevents us from changing the Label's text from another thread.
The correct way to go about this is for the foreign thread to politely ask the UI's thread to update the Label for us. If the UI's thread is busy when the foreign thread makes the request, the request is simply queued and when the UI's thread is finish whatever its doing, its message loop will move on to process the other messages in the queue which would eventually lead it to the request to execute the code that would change the Label's text.
Start by making a function to change the Label's text:-
In this sub we call the Label's InvokeRequired property to determine if the Label is being accessed in the current sub from the UI thread or another thread. It returns True if it was called from a foreign thread.
The above line submits a request to the UI thread to re-run SetLabelText but instead of running it on the foreign thread, it would then execute on UI thread. InvokeRequired would then return False which would execute the Label's text change. This is the key because the UI thread would only execute the sub when its not busy processing some other message.
All that's left now is to change Count:-
vbnet Code:
'
Private Sub Count(ByVal Max As Integer)
For i = 1 To Max
SetLabelText(CStr(i))
Threading.Thread.Sleep(200)
Next
End Sub
We use SetLabelText to change the Label's text instead of trying to change it directly and viola! We have successfully created a simple multi-threaded application.
One thing remains. Execute the program, click the button and close the form before the count is finished. If you did this in the IDE, you would notice that the program doesn't actually stop. That's because the thread is a foreground thread. Usually, we want a background thread for operations like these. Background threads are terminated when the application terminates which is actually when all foreground threads terminate. The UI thread is a foreground thread and by closing the form we terminate that thread but the application won't terminate because the counter is running on another foreground thread. In this case its undesirable so we must make a change:-
vbnet Code:
'
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t1 As New Threading.Thread(AddressOf Count)
t1.IsBackground = True
t1.Start(100)
End Sub
We set IsBackground to True when we create the thread in the Button's Click event handler. Now, the application has only one foreground thread so the application ends when this thread terminates which automatically terminates all background threads.
This is the most basic way to do straight threading in VB.Net. Now there are a couple things I still want to cover like thread pool threads, using threads to create asynchronous methods on classes, and using events to signal when operations on other threads are finished. I'll cover that on a later date in the next post. Until then folks, happy threading !!
Note: The following attachment is the demo project. It was written using VS2008 so you must have VS2008 and later to open it.
Last edited by Niya; Jul 3rd, 2012 at 10:55 PM.
Reason: Minor grammer fix
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Thread Pools
One other thing I want to discuss are thread pools. As defined by wikipedia:-
In computer programming, the thread pool pattern (also replicated workers) is where a number of threads are created to perform a number of tasks, which are usually organized in a queue. The results from the tasks being executed might also be placed in a queue, or if the tasks return no result (for example, if the task is for animation).Typically, there are many more tasks than threads. As soon as a thread completes its task, it will request the next task from the queue until all tasks have been completed. The thread can then terminate, or sleep until there are new tasks available.
Stated simply, they are pre-created threads that may be continually recycled to be used by several tasks waiting in a queue. The need for it arises from the fact that creating new threads require some overhead, more so than re-using older threads. Its faster to re-use a thread than it is to create a new one. There are other performance related advantages to using thread pools beside creation overhead. You can read up more on thread pools in general here at wikipedia. Also, read up on thread pools in .Net here.
Those two links provide most of what you need to know about thread pools so I'm not going to spend any more time talking about it. Lets just go straight to using them in VB.Net.
If we were to alter the example I provided in post #2 to use thread pools, what do you think we need to do ? :-
vbnet Code:
'
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
The above is how we would code the Button's Click event handler to start the count on a thread pool thread. Bet you didn't expect it would be so simple. In fact, its simpler than creating the thread and assigning it a task ourselves. There is no need to instantiate it and no need to make it a background thread. Thread pool threads are background threads by default in .Net.
The QueueUserWorkItem is a shared method of the ThreadPool class. It takes a delegate to a sub that has one parameter of the Object type, just like the Start method of the Thread class.
Multi-Threaded classes
If you examine my example, I'd expect one to conclude that while it works, it isn't very neat. Personally, I'd never actually do multi-threading like that. What if I needed to change some other property of a control ? What if I needed to alter other controls ? Going with the pattern in the example, I'd have to write a function for every property I wish to change on any control because I simply cannot change this property as I've shown, from another thread other than the UI thread. If I wished to change the Text property of three different labels to three different values, I'd need a separate function to change each Label's Text property.
Changing properties across threads requires a function to check the InvokeRequired property before changing the property.
Another problem, what if I needed more than one counter ? Of course, you can simply click the button more than once and it would start multiple counts. But there is a practical problem with that. How do you tell them apart ? What you actually have are several threads running that for all intents and purposes have no identity and this is only magnified when you realize that once a thread pool thread starts, you really have no kind of access to it. It starts and does its thing. You can't peek into it, abort it, pause it, nothing.
Associating a running thread with an instance of a class solves the identity problem. Classes by their very nature have unique identities. If you create say...two TextBox instances and use the Is operator to compare them, it would evaluate to False because while they are the same type of class they are unique individuals. They are the same control but they can have different text, different fonts, colours or sizes. It is the same with any class, you can even give them unique IDs in the form of a property if you so desire. In the next post, we would get into creating a multi-threaded class. Stay tuned.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
For this we are going to use the same count sub but lets make things a little more interesting by changing it into a function that returns a value after its finished.
vbnet Code:
Public Class Counter
Public Function Count(ByVal Max As Integer) As String
Dim startTime As DateTime = DateTime.Now
For i = 1 To Max
Threading.Thread.Sleep(200)
Next
Return "Count took : " + (DateTime.Now - startTime).ToString
End Function
End Class
Our Count sub is now a function that returns a String that states the amount of time it took to complete.
Now start a new Windows Forms project and add a new code file and within it place the above code. On the Form place a Label and a Button and code the Button's Click event handler as:-
vbnet Code:
'
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim cn As New Counter
Label1.Text = cn.Count(20)
End Sub
Run the program and click the button. You'd notice, just like the start of our previous project in post #2, you can't do anything until the count is finished. You can't move the form or click anything on it because the UI thread is currently executing the Count function so it can't process any mouse or keyboard messages until it is free from Count. When Count is finished, it returns a value which we display in the label.
Before applying multi-threading to the class I want to address a limitation of functions like the Start method of the Thread class and QueueUserWorkItem of the ThreadPool class. These methods have a rather annoying limitation of only being able to take the address of functions with only one parameter and Option Strict On would demand this parameter be of the Object type. What if Count had more than one parameter ? We would have to write an overload to take one Object parameter and have that overload call the real Count, getting the parameter from an object made to hold the parameters as fields or properties. If you were to have other multi-threaded methods in the class then you would have to make a class a for the arguments of each method, and a method overload for each to take a single Object parameter made of one of these classes. In other words you have to create two extra entities(a method and a class) for every method you want to call on another thread. This is really retarded, there is just no excuse for that. MS should and could have made Start/QueueUserWorkItem more flexible. Thankfully I came up with a one-size fits all solution. One MS could have easily come up with.
Outside of the Counter class add this class:-
vbnet Code:
'
Public Class ThreadExtensions
Private args() As Object
Private DelegateToInvoke As [Delegate]
Public Shared Function QueueUserWorkItem(ByVal method As [Delegate], ByVal ParamArray args() As Object) As Boolean
Return Threading.ThreadPool.QueueUserWorkItem(AddressOf ProperDelegate, New ThreadExtensions With {.args = args, .DelegateToInvoke = method})
End Function
Private Shared Sub ProperDelegate(ByVal state As Object)
Dim sd As ThreadExtensions = DirectCast(state, ThreadExtensions)
sd.DelegateToInvoke.DynamicInvoke(sd.args)
End Sub
End Class
I'm not going to go into the details of how that works because I'll have to explain way too much that has nothing to do with multi-threading at all. I'll just say what it does. Its simply a version of QueueUserWorkItem that can take the address of any sub or function and an arbitrary amount of arguments using ParamArray so we can call any method with it.
Now going back to our counter class I'd start by adding this function:-
Its a kin function to Count that calls Count on a thread pool thread using our special QueueUserWorkItem. Now our class has two ways to call Count. One is synchronous and one is asynchronous. Notice that we declare the asynchronous one as a sub because it would return immediately after its called hence we cannot get a return value because the count would be taking place on another thread while the calling thread would proceed as normal.
The next thing we want to do is to create a private field in our Counter class:-
vbnet Code:
Private context As Threading.SynchronizationContext = Threading.SynchronizationContext.Current
Think of this object as an anchor into the UI thread. Our asynchronous Count would be running on another thread but remember we may want to report progress back to the UI thread by updating labels or progress bars. As demonstrated in post #2, we can't simply alter a control from any thread other than the UI thread so we need this object to submit methods to be executed on the UI thread. The SynchronizationContext class has a Send method which takes the address of the function we wish to invoke on the UI thread. Send however, suffers from the same limitation like QueueUserWorkItem, it can only the address of a function with only one parameter. We address this in the same manner as we did before:-
vbnet Code:
'
Public Shared Sub ScSend(ByVal sc As Threading.SynchronizationContext, ByVal del As [Delegate], ByVal ParamArray args() As Object)
sc.Send(New Threading.SendOrPostCallback(AddressOf ProperDelegate), New ThreadExtensions With {.args = args, .DelegateToInvoke = del})
End Sub
Add the above shared method to the ThreadExtensions class. We will use the above method to execute methods on the UI thread so we can alter controls in the proper thread safe manner.
Ok....so we're practically set. Now we need to set up a way to report the progress of the count and return the function's value. In the earlier example in post #2, we used a function to set the Label's text to reflect a where the count has reached. Now we don't want to have to write a separate function for every control we may wish to alter so a far more agreeable solution would be to raise some kind of event on the UI thread and then we are free to do anything to any control and all controls in the normal way. No need to call InvokeRequired to check for thread safety anymore. We pass of that responsibility to our Counter class.
Now, lets get to coding the events. In this I tend to stick to the patterns of setting up events that is used by MS in the .Net Framework. So we start by creating an EventArgs based object:-
vbnet Code:
'
Public Class CountChangedEventArgs
Inherits EventArgs
Private _CurrentCount As Integer
Private _Max As Integer
Public Sub New(ByVal cc As Integer, ByVal max As Integer)
_CurrentCount = cc
_Max = max
End Sub
Public ReadOnly Property CurrentCount() As Integer
Get
Return _CurrentCount
End Get
End Property
Public ReadOnly Property Max() As Integer
Get
Return _Max
End Get
End Property
End Class
Place the above class outside of the Counter class.
Now add these lines of code to the Counter class:-
vbnet Code:
'
Public Event CountChanged As EventHandler(Of CountChangedEventArgs)
Protected Overridable Sub OnCountChanged(ByVal e As CountChangedEventArgs)
RaiseEvent CountChanged(Me, e)
End Sub
Our Counter class now has a CountChanged event which we would now raise. Remember, we should raise the event on the UI thread so any code in the event handler would be free to alter controls without worrying about cross-thread calls:-
vbnet Code:
'
Public Function Count(ByVal Max As Integer) As String
Dim startTime As DateTime = DateTime.Now
Dim e As CountChangedEventArgs
For i = 1 To Max
e = New CountChangedEventArgs(i, Max)
If context Is Nothing Then
OnCountChanged(e)
Else
ThreadExtensions.ScSend(context, New Action(Of CountChangedEventArgs)(AddressOf OnCountChanged), e)
End If
Threading.Thread.Sleep(200)
Next
Return "Count took : " + (DateTime.Now - startTime).ToString
End Function
Change the Count sub to the above. We use the ThreadExtensions function we wrote earlier to have the SynchronizationContext object raise the event on the UI thread for us in the case context is not Nothing. If context is Nothing we simply raise the event in the normal way. I do not understand the SynchronizationContext enough to tell you the circumstances under which it would be Nothing but in the most common scenarios it should have a proper object.
The only thing remaining now is that return value. We simply use another event. Like before we create a new EventArgs object:-
vbnet Code:
'
Public Class CountCompletedEventArgs
Inherits EventArgs
Private Dim _message As String
Public Sub New(ByVal msg As String)
_message = msg
End Sub
Public ReadOnly Property Message() As String
Get
Return _message
End Get
End Property
End Class
And add the following to the Counter class:-
vbnet Code:
'
Public Event CountCompleted As EventHandler(Of CountCompletedEventArgs)
Protected Overridable Sub OnCountCompleted(ByVal e As CountCompletedEventArgs)
RaiseEvent CountCompleted(Me, e)
End Sub
Now we change our Count function yet again:-
vbnet Code:
'
Public Function Count(ByVal Max As Integer) As String
Dim startTime As DateTime = DateTime.Now
Dim e As CountChangedEventArgs
Dim msg As String
For i = 1 To Max
e = New CountChangedEventArgs(i, Max)
If context Is Nothing Then
OnCountChanged(e)
Else
ThreadExtensions.ScSend(context, New Action(Of CountChangedEventArgs)(AddressOf OnCountChanged), e)
End If
Threading.Thread.Sleep(200)
Next
msg = "Count took : " + (DateTime.Now - startTime).ToString
ThreadExtensions.ScSend(context, New Action(Of CountCompletedEventArgs)(AddressOf OnCountCompleted), New CountCompletedEventArgs(msg))
End If
Return msg
End Function
Our Count function now raises a completed event and passes the return value using the EventArgs object. Our Counter class in now has multi-threaded capabilities.
Now go back to the Form's code and change it to this:-
vbnet Code:
Public Class Form1
Private WithEvents m_cn As New Counter
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
m_cn.CountAsync(100)
End Sub
Private Sub m_cn_CountChanged(ByVal sender As Object, ByVal e As CountChangedEventArgs) Handles m_cn.CountChanged
Label1.Text = CStr(e.CurrentCount)
End Sub
Private Sub m_cn_CountCompleted(ByVal sender As Object, ByVal e As CountCompletedEventArgs) Handles m_cn.CountCompleted
MsgBox(e.Message)
End Sub
End Class
The Form must have a Label and a Button. Run the application and click the button and you should see the count progressing indicated by the label and a message box pop up when the count is done, showing the return value which is the time the count took to complete. So there you have it. A working multi-threaded class. There is a little more I'd like to write on this subject but I'm too near the 15K character limit for this post so I'll have to cut it here. Please feel free to post any questions you may have in the thread. Good luck
Last edited by Niya; Jul 19th, 2012 at 01:37 PM.
Reason: Minor correction
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
You're welcome . The first two posts covers the heart of it. My next post will basically show how you can organize that neatly into classes to create multi-threaded components. It will take what I've already discussed and twist it up a little and it would also cover thread pools. I'll try to get it up within this week.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Fair questions. There is nothing wrong with not knowing
Originally Posted by marniel647
why do we use threading.thread.sleep
Thread.Sleep suspends the thread. I did it in that example because without it the count would have happened too fast for anyone to see what was happening. It should be noted that there is almost no practical reason to ever use Thread.Sleep in real applications. Its great for examples to demonstrate stuff but has no real benefits aside from that.
Originally Posted by marniel647
what does this bold part do t1.start(100)?
Remember I did this:-
vbnet Code:
Dim t1 As New Threading.Thread(AddressOf Count)
That line created the Thread object. The constructor takes the address of the Count sub. If you look back you would see that sub Count takes one argument, which is the number to count up to before the loop finishes. When you call Start it allows you to pass one parameter to whatever sub its expected to execute on a different thread, in this case its Count. So:-
vbnet Code:
t1.Start(100)
That tells the thread object to call Count passing in 100 to its parameter. In other words it effectively does this:-
vbnet Code:
Count(100)
Except that it runs on another thread. Makes sense ?
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Fair questions. There is nothing wrong with not knowing
Thread.Sleep suspends the thread. I did it in that example because without it the count would have happened too fast for anyone to see what was happening. It should be noted that there is almost no practical reason to ever use Thread.Sleep in real applications. Its great for examples to demonstrate stuff but has no real benefits aside from that.
Remember I did this:-
vbnet Code:
Dim t1 As New Threading.Thread(AddressOf Count)
That line created the Thread object. The constructor takes the address of the Count sub. If you look back you would see that sub Count takes one argument, which is the number to count up to before the loop finishes. When you call Start it allows you to pass one parameter to whatever sub its expected to execute on a different thread, in this case its Count. So:-
vbnet Code:
t1.Start(100)
That tells the thread object to call Count passing in 100 to its parameter. In other words it effectively does this:-
vbnet Code:
Count(100)
Except that it runs on another thread. Makes sense ?
thanks for your explanation now i know much the basic of multithreading
thank you very much.
Well, a return value is the issue here isn't it. With Option Strict Off you can actually use a function with only one parameter but then what's the point. The function is executed on another thread so the thread that started isn't blocked waiting for a return value. The correct way to return a value would be to have the function itself signal its own completion and return a value by using a call back. My preferred approach is to implement the functionality that I wish to multi-thread into a class and use events to signal completion and return results. My reserved post is to write about and demonstrate that very thing. I'll try to get it up on Saturday, do you urgently need this information ?
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Yea I've already begun writing it. I got the section on thread pool threads already completed. I'm going to complete it today. Hope it doesn't disappoint
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
great update Niya. I've been having trouble with implementing multi-threaded classes so your write up has been very helpful. I do have a quick question. What if I want to cancel the running operation in the middle?
Its quite easy. Create a method to do it. In the case of the Counter class example, I'd make a CancelCountAsync method. I would then make a private boolean field called CountAsyncCancelled. When the CancelCountAsync method is called it would simply set our CountAsyncCancelled to True. The counter loop would be checking this on every iteration and if its True, we simply raise an event and exit the loop or return from the Count function. You could opt to create a cancel type event or use the completed event after adding something to the EventArgs object to indicate that the operation was cancelled.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
wow that was easy. It took about 2 minutes to implement a cancel button in your counter example. I'm actually a little embarrassed that I didn't figure that out . Thanks for your help Niya.
That's understandable. The most natural instinct would be to stop the thread itself but its easy to lose sight of the fact that simply ending the function/sub the thread is running will end the thread itself which is what would essentially happen when a cancel is implemented the way I suggested.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Hello Niya, can you help me in my vb multithreaded application? I need to some help because this argument is a bit hard. I'm making a network application (server/client), and in the server app code I have to use multithreading. First, I made a new class which is composed by Tcp sockets code and some of graphical elements (declared after class declaration, a panel, a picture box and a button). I've declared an TCPlistener too, but It make his work into a BackGround Worker. It is composed by a do-loop cycle, which check for new connections, and when a new connection is coming, it create a new instance of my class... Now the problem it's how to interact with GUI from the class istances... how can i insert graphical elements to the GUI and control them? Thanks for the guide! =)
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Thanks Niya, I'm working through your samples and are finding it quite enjoyable and very easy to understand, keep up the good work!
Thanks, I'm glad you found it helpful
Originally Posted by psoftware
Exactly! You understood me!! Can you help me?
Hmm....Well this is a little tricky. As I've shown in my samples, I like to communicate with the UI thread by raising events on the UI thread.
Now I assume you have the BackgroundWorker component on a Form and in the DoWork event, your classes are being created. DoWork runs on another thread so classes created there will be on that thread. What you could do is create a constructor for your class that takes a SynchronizationContext object which it could use to raise events on the UI thread. You then create a private field in the Form itself which would hold the SynchronizationContext for the UI thread:-
vbnet Code:
Public Class Form1
Dim context As SynchronizationContext = SynchronizationContext.Current
'Form code
End Class
Use that context variable when calling the constructor of your class within the DoWork event. Observe my samples to learn how to use the SynchronizationContext to raise events on the thread it belongs to.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
I tried to implement your guide in my code, but now there's some problems... I created some events and inserted your ThreadExtensions class, but when i try to raise an event (following your guide), context variable is null, and because of this, the event is raised in the background thread..... I inserted a MsgBox(Thread.CurrentThread.ManagedThreadId) in the load handle sub of my main window, and another in the events which I created, and the results are not equals... Why is context variable in null?
context would be null if you if you called Current on a thread without a SynchronizationContext object. You indicated that the object itself is being created on another thread so I'd expect it to be null. When a VB.Net program starts one of the things it does is create a SynchronizationContext for the UI thread. Like I said in my guide, think of it like an anchor. You wish to raise events on the UI thread so you get one from the UI thread by calling SynchronizationContext.Current on the UI thread. Since you create your objects on the thread pool thread that the BackGroundWorker uses in DoWork, the object's constructor would called on that thread which has no SynchronizationContext object. This is why I said in post #26 to declare it on the Form where the BackGroundWorker resides and create a constructor for your class that can take a SynchronizationContext object and when you create a new object in the DoWork event you call that constructor an pass the SynchronizationContext variable you declared on the Form.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
It's a bit hard because I have to translate and to read and read what you write... Now I understood you and I implemented this code in my project, It works fine. But I noticed only first created class can raise events, some others can't (handles subs aren't called)....In my form class I implemented the AddHandler function to make some functions handle my new class events. I don't understand this thing, It's very strange and make me angry!
Sorry If I'm doing a lot of questions to you, but It's a bit hard argument and I don't know how to solve this problems. I hope you won't hate me!
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Mmmmmmh yes! Some classes, can't raise events, and I don't know why...
This is my DoWork Sub
Code:
Private Sub ChechConnectionsThread_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles ChechConnectionsThread.DoWork
While True
Dim clientuser As New RemoteClient(ClientScreenSettings.TCPlistener.AcceptTcpClient, GenIndex(), context)
AddHandler clientuser.ClientConnected, AddressOf ClientConnectEvent
AddHandler clientuser.ClientDisconnected, AddressOf ClientDisconnectEvent
AddHandler clientuser.ClientScreenChanged, AddressOf ClientSendScreen
AddHandler clientuser.ClientSessionChanged, AddressOf ClientSessionChanged
End While
End Sub
and this is one of the events...
Code:
Private Sub ClientConnectEvent(ByVal sender As System.Object, ByVal e As ClientConnectedEventArgs)
MsgBox(e.ClientName + "id: " + e.ClientIndex.ToString + " has connected")
End Sub
I call the event from classes from this sub
Code:
RaiseEventConnected()
Code:
Public Sub RaiseEventConnected()
Dim e As New ClientConnectedEventArgs(_ClientNick, _ClientIndex)
If context Is Nothing Then
OnClientConnectedEventArgs(e)
Else
ThreadExtensions.ScSend(context, New Action(Of ClientConnectedEventArgs)(AddressOf OnClientConnectedEventArgs), e)
End If
End Sub
ClientConnectedEventArgs and OnClientConnectedEventArgs are coded like your code... Is there something wrong?
Whoa You do realize that by you can only have one RemoteClient object that way right ?
Every time that loop iterates, the last RemoteClient object you created is up for garbage collection so its no wonder only one is raising events. As far as I see you implemented the threading properly so that's not the problem.
Now, just get your hands dirty and complicate this a little more. Store every new RemoteClient you create in a List(Of T) declared as a private field of the Form. This would ensure that every RemoteClient created in that While loop is not lost but have active references:-
vbnet Code:
Public Class Form1
Dim ConnectedClients As New List(Of RemoteClient)
Private Sub ChechConnectionsThread_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles ChechConnectionsThread.DoWork
While True
Dim clientuser As New RemoteClient(ClientScreenSettings.TCPlistener.AcceptTcpClient, GenIndex(), context)
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
It shouldn't be a problem. Actually using a static field to record the connected clients is a very good idea. At this point I'm beginning to believe that your problem isn't related to threading but to some implementation detail elsewhere. Post all the code for the RemoteClient class as well as all the form code where the BackGroundWorker resides.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Ehm.... Should I take the SynchronizationContext from the BackgroundWorker and use it with ScSend sub and my class function?
Ok, I'm a little lost now . I think you may be misunderstanding something fundamental here though. Tell me what sub you wish to call, what class it belongs to and where you are calling it from. I need some more details, I'm not really clear on what your concern is.
Originally Posted by psoftware
If I want to call subs from the UI thread to the BackgroundWorker Thread and to the classes?
This statement makes no sense, you don't call subs from somewhere to somewhere. I just need you to clarify what you mean
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber