VB.Net TcpClient error - The requested address is not valid in its context - vb.net

I am trying to implement a TcpClient and keep getting the error
The requested address is not valid in its context
This, however, only happens if I place the code inside a class that is called by the main form. When I place the code in the main form (in the form load event), the error does not occur and communication over the TcpClient is successfull.
I have tried to decorate the class with Inherits ApplicationContext but the error is still thrown. heres the code I am using:
The class
Public Class dasharClient
Inherits ApplicationContext
Public port As Integer
Public hostname As String
Private myClient As TcpClient
Sub New()
myClient = New TcpClient(hostname, port)
Dim subscribe As Byte() = Encoding.ASCII.GetBytes("Hello World")
myClient.GetStream.BeginWrite(subscribe, 0, subscribe.Length, AddressOf MyWriteCallBack, myClient.GetStream)
End Sub
Public Sub MyWriteCallBack(ByVal ar As IAsyncResult)
CType(ar.AsyncState, NetworkStream).EndWrite(ar)
End Sub
End Class
I initialize the class from the main form thus
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim testClass As New dasharClient With {.port = 3354, .hostname = "my.domain.com"}
End Sub
As noted above, if I put the code inside the class into the form load event sub (without the class decoration), it works!

Related

VB.NET: Thread-join never returns?

my problem is that in a WCF service (on a network call) the thread doesn't come back. Unfortunately, I don't get any return values for functions or code after the join method is not executed.
The WCF service itself has a web interface that creates a thread because a form with TreeView and AllowDrop activated there would later return an error:
System.InvalidOperationException: "DragDrop registration failed"
ThreadStateException: STA mode (Single Thread Apartment) must be set for the current thread before OLE calls can be made. Make sure that the main function is marked with STAThreadAttribute.
If, on the other hand, I start a sample project "WindowsApp56" (no WCF service), Form1 is opened by ClassLibrary7 and code according to the join method is activated. The message box "ready" is now displayed.
No errors are thrown with the web service, when the thread isn't coming back.
Project WindowsApp56:
Imports System.Threading
Imports ClassLibrary7
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim tmp As New Class1
Dim myobject As System.Threading.Thread = New Thread(New ThreadStart(AddressOf tmp.Main))
myobject.TrySetApartmentState(ApartmentState.STA)
myobject.Name = "Tester1"
myobject.Start()
If myobject.ThreadState <> ThreadState.Unstarted Then
myobject.Join()
End If
MsgBox("ready")
End Sub
End Class
ClassLibrary7:
Public Class Class1
Public Sub Main()
Dim a As New Form1
a.ShowDialog()
End Sub
End Class
Is the thread join method wrong? How can I "wait for" return values.
According to the error message: 'STA mode (Single Thread Apartment) must be set for the current thread before OLE calls can be made. Make sure that the main function is marked with STAThreadAttribute.'
Add the STAThreadAttribute attribute on the Main method.
Public Class Class1
<STAThread()>
Public Sub Main()
Dim a As New Form1
a.ShowDialog()
End Sub
End Class

Change UI parameters using thread in separate class

I would like to be able to change an element of the main form inside a thread declared in a separated class (In this case I want to change a label text).
I tried the following code:
Form1:
Imports System.Threading
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim SecondClassObject As New SecondClass()
End Sub
End Class
SecondClass:
Imports System.Threading
Public Class SecondClass
Public Thread As New Thread(AddressOf Increment)
Public counter As Integer = 0
Sub New()
Thread.Start()
End Sub
Sub Increment()
While True
Form1.Label1.Text = counter
counter += 1
End While
End Sub
End Class
If I do the same thing using a thread but in the form code itself than the label text will change:
Imports System.Threading
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim thread As New Thread(AddressOf Increment)
thread.Start()
End Sub
Sub Increment()
While True
Label1.Text = counter
counter += 1
End While
End Sub
End Class
How should I do in order to archieve the same result using a thread in a separated class?
First, I do want to point out that you need to use an Invoke/Callback to safely set the label's text from the secondary thread. I don't know if you're doing that in your actual code base, but wanted to specify anyways.
Now, focused on the actual question, I believe that the easiest way to do as requested is to pass a reference to the original instance of Form1 to your SecondClass. Having a reference to the parent, means that you would be able to manipulate the parent's publicly exposed elements as needed.
Consider the below:
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim secondClass As New SecondClassObject(me)
End Sub
Delegate Sub SetTextCallback (value as String)
Public Sub SetText (value as string)
if me.Label1.InvokeRequired Then
dim d as New SetTextCallback(addressOf SetText)
Me.Invoke(d, New Object() {value})
Else
me.label1.text = value
End If
End Sub
End Class
Public Class SecondClassObject
private _parent as Form1
private myThread As New Thread(AddressOf Increment)
Public Sub New (byref p as Form1)
me._parent = p
myThread.Start()
End Sub
Sub Increment()
While True
Me._parent.SetText(counter)
counter += 1
End While
End Sub
End Class
What is happening is that the a reference to the parent is passed into the second class as a constructor, doing so allows us to interact with the parent from the second class.
Now, that is one way, but other options do exist. Things such as specialized events/handlers or wiring up databinding between the Form1.Label1 and a property exposed from the SecondClassObject. Even a singleton pattern, where the value to be incremented is shared between all instances, so when the SecondClassObject increments it, Form1 would be aware and know to update Label1.
Also, please note that the above code is for example purposes, and is missing things such as a defined declaration for Label1.
On windows it's not possible to change the UI from a non UI thread.
It looks like that you have to use Control.Invoke or better Control.BeginInvoke.
The problem with using Control.Invoke is that it's executed on the UI thread and the calling thread waits for completion. Which would be bad when your background worker continusly does some computations.

Change UI from another class created in a thread in vb.net

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?
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

NSQ vb.net MessageHandler

I am trying to use this package in vb.net NsqSharp
There is a good code for it in C# but i need it in vb.net.
I got it to send a message to my NSQ server, but the problem is to get it.
But i get a error on consumer.AddHandler(New HandleMessage()) and i do not know if i declare the HandleMessage right.
Imports NsqSharp
Imports System.IO
Imports System.Text
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim producer = New Producer("127.0.0.1:4150")
producer.Publish("test-topic-name", Me.txt_tx.Text)
producer.Stop()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim consumer = New Consumer("test-topic-name", "channel-name")
consumer.AddHandler(New HandleMessage())
consumer.ConnectToNsqLookupd("127.0.0.1:4161")
consumer.Stop()
End Sub
Public Interface IHandler : End Interface
Public Sub HandleMessage(message As Message)
Dim msg As String = Encoding.UTF8.GetString(message.Body)
MsgBox(msg)
End Sub
Public Sub LogFailedMessage(message As Message)
Dim msg As String = Encoding.UTF8.GetString(message.Body)
MsgBox(msg)
End Sub
End Class
But i get a error on Implements IHandler
Lovely description of the problem, you can't get useful answers when you don't describe the exact error message you see. You did write the code wrong, VB.NET requires the Implements keyword on interface method implementations. You'd normally fall in the pit of success by letting the IDE generate these methods for you. As soon as you type "Implements IHandler" and press the Enter key, the IDE automagically adds the methods.
So there's probably something wrong with the library reference as well. Steps one-by-one:
Tools > Nuget Package Manager > Package Manager Console.
Type "Install-Package NsqSharp". Watch it trundle while it downloads and installs the package.
Put Imports NsqSharp at the top of the source file.
You should now end up with:
Public Class MessageHandler
Implements IHandler
Private Sub IHandler_HandleMessage(message As Message) Implements IHandler.HandleMessage
Dim msg As String = Encoding.UTF8.GetString(message.Body)
MessageBox.Show(msg)
End Sub
Private Sub IHandler_LogFailedMessage(message As Message) Implements IHandler.LogFailedMessage
Dim msg As String = Encoding.UTF8.GetString(message.Body)
MessageBox.Show(msg)
End Sub
End Class

Set Label From Thread

Form1.vb
Imports System.Threading
Public Class Form1
Dim demoThread As Thread
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Start As New Class1
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Start.ThreadProcSafe))
Me.demoThread.Start()
End Sub
Delegate Sub SetTextCallback([text] As String)
Public Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the
' calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
End Class
Class1.vb
Public Class Class1
Public Sub ThreadProcSafe()
Form1.SetText("This text was set safely.")
End Sub
End Class
Can someone tell me why this doesn't update the textbox?
It works when ThreadProcSafe is called when its inside Form1(and is still started by a thread) but when it's moved outside of the class into another, no warnings or errors but doesn't update.
The reason is that you are referring to the default instance in your second code snippet. Default instances are thread-specific so that second code snippet will create a new instance of the Form1 type rather then use the existing instance. Your Class1 needs a reference to the original instance of Form1.
If that's not practical then the solution is to not do the delegation in the form but rather do it in the class accessing the form, using the SynchronizationContext class.