Reputation: 65
When the form loads, it stars a thread to find all the computers in the network with the use of a library, then for each computer it creates a class which is stored in a list, that class handles the TCP communication between the computer and the remote end, when data is received i want to show it on my form
The code looks something like this
Public Class FormHub
Public Sub ChangeUI (ByVal Text as String)
.....
End Sub
Private Sub FormHub_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim thr As New Thread(AddressOf FindComputers)
thr.Start()
End Sub
Sub FindComputers()
For Each Computer As String In APINetworkItems.GetAllComputersInDomain
For Each Address As IPAddress In Dns.GetHostEntry(Computer).AddressList
If Address.AddressFamily = AddressFamily.InterNetwork Then
Dim handler As New RemoteEnd
handler.Connect(New IPEndPoint(Address, Port), Address, Computer)
ConnectionList.Add(handler)
End If
Next
Next
End Sub
Public Class RemoteEnd
Public Sub Connect(ByVal EndPoint As IPEndPoint, ByVal IP As IPAddress, ByVal Name As String)
.........
End Sub
Public Sub Receive()
....
<Here i want to call a sub on the Form>
End Sub
End Class
Googled it, nothing seems to work... what do i do?
Upvotes: 0
Views: 203
Reputation: 1839
This is the most common problem for people who are just starting to understand multi threading. Think about how WinForm controls interact with calling elements. They use events to signal to the outside world that something happened within them. You can do the same:
Public Class SomeForm
Private connectionsList As New List(Of RemoteEnd)
Public Property Port As Integer
Sub FindComputers()
For Each comp As String In APINetworkItems.GetAllComputersInDomain
For Each addr As IPAddress In Dns.GetHostEntry(comp).AddressList.Where(Function(a) a.AddressFamily = AddressFamily.InterNetwork)
Dim remote As New RemoteEnd
' Add a handler to handle the Connected event that the RemoteEnd class exposes, and then call its Connect sub.
' Note that we do not add the instance to the list yet, as it's not really connected yet (not as long as the RemoteEnd class
' hasn't raised the Connected event...)
AddHandler remote.Connected, AddressOf RemoteEnd_Connected
remote.Connect(New IPEndPoint(addr, Port), addr, comp)
Next
Next
End Sub
Private Sub RemoteEnd_Connected(ByVal sender As Object, ByVal e As EventArgs)
' When the form catches the event, it restores the reference to the instance that raised it, and
' add the instance to the list. Keep in mind that the event will be handled on the same thread it was raised!
' That means that if you want to display data in a form control, you need to invoke the form to make the change!
' Here we just add a reference to a list, so it doesn't matter.
Dim remote = DirectCast(sender, RemoteEnd)
connectionsList.Add(remote)
DoSomething(remote)
End Sub
Private Sub DoSomething(ByVal remote As RemoteEnd)
' ...
End Sub
End Class
Public Class RemoteEnd
Public Event Connected(ByVal sender As Object, ByVal e As EventArgs)
Public Sub Connect(ByVal EndPoint As IPEndPoint, ByVal IP As IPAddress, ByVal Name As String)
' To work efficiently, when this sub is called we need to start the asynchronous process and return immediately.
' When the connection is fully handled, we will raise the event and carry a reference to this instance to the form.
' Because QueueUserWorkItem only takes in one state object to pass parameters, we create a single object that
' contains all the information needed to connect and pass that.
Dim params = New ConnectionInfo(EndPoint, IP, Name)
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf HandleConnectionAsync), params)
End Sub
Private Sub HandleConnectionAsync(ByVal connectionInfos As ConnectionInfo)
' ...
' Here we raise the Connected event for the outside world, carrying a reference to this instance,
' and possibly an instance derived from EventArgs. Here we return nothing.
RaiseEvent Connected(Me, Nothing)
End Sub
End Class
Public Class ConnectionInfo
Public Property EndPoint As IPEndPoint
Public Property IP As IPAddress
Public Property Name As String
Public Sub New(ByVal _ep As IPEndPoint, ByVal _ip As IPAddress, ByVal _name As String)
EndPoint = _ep
IP = _ip
Name = _name
End Sub
End Class
Your RemoteEnd class has no reason whatsoever to even be aware of the form, this is very important, because you want each class of yours to be loosely coupled to others. If a class depends on another, they both should be in the same assembly, but if not they should be separate, so that they can be reused elsewhere. If your form depends on your class, and your class depends on your form, it's called codependency, and it is very bad from an architectural point of view. It might work, but it will be hell to maintain.
As for your original question, once you are setup with the above code, you will notice that the code in the RemoteEnd_Connected handler is actually executed on the same thread that we created on the threadpool in the RemoteEnd class. That means that within that handler, you cannot play with UI controls, because they are on another thread. You need to ask the form to call the delegate with the parameters you need:
Private Delegate Sub SetTextDelegate(ByRef ctrl As Control, ByVal text As String)
Private delSetText As New SetTextDelegate(AddressOf SetText)
Private Sub SetText(ByRef ctrl As Control, ByVal text As String)
ctrl.Text = text
End Sub
Private Sub DoSomething()
If Me.InvokeRequired Then
Me.Invoke(delSetText, {SomeTextBox, "This is the text to set..."})
Else
SomeTextBox.Text = "This is the text to set..."
End If
End Sub
Upvotes: 1