Fast TCP receive - vb.net

I'm working on a TCP client. but for some reason it is not fast enough receiving data. If the server sends a string (about 140 characters) every 50ms it works fine, but it I change it to 10ms (or lower) the Client doesn't display every string.
I tried the same with another TCP chat program, and there 1ms was no problem.
So it should be possible ;-)
Below my code:
Imports System.Net, System.Text
Imports System.Net.Sockets
Imports System.IO
Imports System.Xml
Imports System.Globalization
Imports System.ComponentModel
Imports System.Management
Imports System.Text.RegularExpressions
Public Class Client
Dim t As New TcpClient
Private Sub Client_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Control.CheckForIllegalCrossThreadCalls = False
Try
t.Connect("127.0.0.1", "50020")
If t.Connected Then
t.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf doread, Nothing)
login()
StatusStrip1.BackColor = Color.LightGreen
End If
Catch ex As Exception
Application.Restart()
End Try
End Sub
Sub login()
senddata("LOGIN|")
End Sub
Sub doread(ByVal ar As IAsyncResult)
Try
If t.Connected = True Then
Dim sr As New StreamReader(t.GetStream)
Dim msg As String = sr.ReadLine()
TextBox1.Text = (msg & vbCr & vbLf) & TextBox1.Text
t.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf doread, Nothing)
End If
Catch ex As Exception
Application.Restart()
End Try
End Sub
Sub senddata(ByVal message As String)
Dim sw As New StreamWriter(t.GetStream) 'declare a new streamwriter
sw.WriteLine(message) 'write the message
sw.Flush()
End Sub
End Class

You might not be aware of the fact that TCP connections represent a stream of bytes. Messages/packets are not preserved. You might receive as little as one byte at a time.
You assume that you are receiving whole lines.
Also, this hybrid use of binary reads and StreamReader cannot work for many reasons. One of them is that StreamReader buffers internally. Each time you create a new one you throw buffer contents away.
Why are you checking t.Connected all the time? If false, you simply do nothing. This is worse than not checking at all. Don't blindly copy code from the web (I know that you copied this because this broken pattern is all over the web and nobody would normally think of doing this).
Application.Restart
??? That's not an appropriate error strategy. Better: Dispose of all resources and show a message box.
This is really, really broken. Unsalvagable. Need to rewrite.
Simply do this on a background thread:
var sr = new StreamReader(...);
while (true) {
var line = sr.ReadLine();
if (line == null) break;
Invoke(new Action(() => TextBox1.Text += line));
}
Or anything equivalent.

Related

TCP server, why is the send message / string not recognized correctly?

This code is for a TCP server that executes something if a certain message is received.
My problem is that the received string doesn’t match the one inside the If statement.
If txt = "reset" Then
Label2.Text = "merda"
End If
Label1.Text = txt
All the messages I send are displayed in Label1 correctly but if I send the "reset" the IF statement doesn’t recognize the string and display "merda" on the label2.
There must be something else hidden in the string along with the display text but I don’t seem to understand what.
I’m using Hercules by HW Group to as client to send the messages. I’ve tried other software but the result is always the same
I ask for your advice fixing this.
Thanks
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Public Class Form1
Dim _server As TcpListener
Dim _listOfClients As New List(Of TcpClient)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
_server = New TcpListener(IPAddress.Parse("127.0.0.1"), 1234)
_server.Start()
Threading.ThreadPool.QueueUserWorkItem(AddressOf NewClient)
Catch ex As Exception
MsgBox(ex.Message)
End Try
CheckForIllegalCrossThreadCalls = False
End Sub
Private Sub NewClient(state As Object)
Dim client As TcpClient = _server.AcceptTcpClient()
Try
_listOfClients.Add(client)
Threading.ThreadPool.QueueUserWorkItem(AddressOf NewClient)
While True
Dim ns As NetworkStream = client.GetStream()
Dim toReceive(1000) As Byte
ns.Read(toReceive, 0, toReceive.Length)
Dim txt As String = Encoding.ASCII.GetString(toReceive)
If txt = "reset" Then
Label2.Text = "merda"
End If
Label1.Text = txt
End While
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
End Class

vb.net winforms download without blocking

I'm downloading files .mp3 and my goal is not to have even a minimum GUI freezing during downloading.
My aim is also to display the bites received in a progress bar and through labels.
This code is working, but sometimes is freezing without any reason, sometimes the progress bar doesn't work until file is completely done.
So far, this is the "best" code I found online for a completely working progress bar during a download, but still gets problems.
How do you think I can increase performances? How can I make a resistant and reliable working progressbar? How can I download also large file without GUI freezing? I tried (curiosity) to download a 600 mb file and it completely freeze, not responding and not giving any issue back.
Thanks
EDIT1: I'm trying with this,eventhough I'm lost on high waves.. Any idea on how can I use this code and insert it into Jimi Answer? Answer
Imports System.IO
Imports System.IO.Path
Imports System.Net
Public Class Form1
Private downloader As MyDownloader = Nothing
Private Sub btnStartDownload_Click(sender As Object, e As EventArgs) Handles btnStartDownload.Click
Dim progress = New Progress(Of String)(
Sub(data)
MsgBox("we are on the UI thread here")
End Sub)
Dim url As Uri = New Uri(TextBox1.Text)
downloader = New MyDownloader()
'How can I remove this second? I don't need download from url every 1 second.
downloader.StartDownload(progress, url, 1)
End Sub
And
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Net
Imports System.Net.Http
Imports System.Text.RegularExpressions
Imports System.Threading
Public Class MyDownloader
Private Shared ReadOnly client As New HttpClient()
client.DownloadProgressChanged += AddressOf Client_DownloadProgressChanged
client.DownloadFileCompleted += AddressOf Client_DownloadFileCompleted
Private interval As Integer = 0
Private Sub Client_DownloadFileCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
System.Windows.Forms.MessageBox.Show("Download OK!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
Public Sub StartDownload(progress As IProgress(Of String), url As Uri, intervalSeconds As Integer)
interval = intervalSeconds * 1000
Task.Run(Function() DownloadAsync(progress, url))
End Sub
Private Sub Client_DownloadProgressChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
ProgressBar1.Minimum = 0
Dim receive As Double = Double.Parse(e.BytesReceived.ToString())
Dim total As Double = Double.Parse(e.TotalBytesToReceive.ToString())
Dim percentage As Double = receive / total * 100
label2.Text = $"{String.Format("{0:0.##}", percentage)}%"
ProgressBar1.Value = Integer.Parse(Math.Truncate(percentage).ToString())
End Sub
Private Async Function DownloadAsync(progress As IProgress(Of String), url As Uri) As Task
Dim pattern As String = "<(?:[^>=]|='[^']*'|=""[^""]*""|=[^'""][^\s>]*)*>"
Dim downloadTimeWatch As Stopwatch = New Stopwatch()
downloadTimeWatch.Start()
Do
Try
Dim response = Await client.GetAsync(url, HttpCompletionOption.ResponseContentRead)
Dim data = Await response.Content.ReadAsStringAsync()
data = WebUtility.HtmlDecode(Regex.Replace(data, pattern, ""))
progress.Report(data)
Dim delay = interval - CInt(downloadTimeWatch.ElapsedMilliseconds)
Await Task.Delay(If(delay <= 0, 10, delay))
downloadTimeWatch.Restart()
Catch ex As Exception
End Try
Loop
End Function
End Class
I'm Seriously lost on it, I tried to delete cancel download as I am not going to stop any download and I tried also to delete Download from url every 1 second as I just need one time download for every link.
Thanks

Delay when displaying a message received by a Telnet client

I am trying to implement a Telnet client in VB.NET. I am following this code as example:
The program I'm implementing works as follows:
I click the button "Open Telnet" to open the Telnet session.
I write an instruction (string) in the text box on the left and then I click on Button1 to send the message to a Telnet server (an electronic board with an embedded Ethernet port).
The answer sent by the Telnet server is displayed in the text box on the left.
The problem I'm having with both the example and the implementation I'm doing is that the messages are displayed delayed. For example, if I send the string 1FFFFFF + vbCrLf I am supposed to receive a message from the server saying Unknown HMI code!. I have checked with Wireshark that the message is sent by the Telnet server just after I sent the instruction with the VB.NET program but it is shown in the text box on the right only if I click Button1 a second time (no matter what is written in the text box on the left).
Could you please tell me if there is something I'm missing in the code?
Below is my code:
Imports System
Imports System.IO
Imports System.Net.Sockets
Imports System.Security.Cryptography.X509Certificates
Imports System.Text
Imports System.Threading
Imports System.Net.Http
Imports System.Net.Security
Imports System.Net.IPAddress
Imports System.Net
Public Class Form1
' Create a TcpClient.
Dim client As New TcpClient
Dim stream As NetworkStream
' Function to write/read a TCP stream.
Shared Sub Connect(server As [String], message As [String])
Try
' Translate the passed message into ASCII and store it as a Byte array.
Dim data As [Byte]() = System.Text.Encoding.ASCII.GetBytes(message)
' Send the message to the connected TcpServer.
Form1.stream.Write(data, 0, data.Length)
Console.WriteLine("Sent: {0}", message)
' Buffer to store the response bytes.
data = New [Byte](256) {}
' String to store the response ASCII representation.
Dim responseData As [String] = [String].Empty
' Read the first batch of the TcpServer response bytes.
Dim bytes As Int32 = Form1.stream.Read(data, 0, data.Length)
responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes)
Console.WriteLine("Received: {0}", responseData)
Form1.TelnetRx.Text += responseData + vbCrLf
Form1.TelnetRx.Refresh()
Catch e As ArgumentNullException
Console.WriteLine("ArgumentNullException: {0}", e)
Catch e As SocketException
Console.WriteLine("SocketException: {0}", e)
End Try
Console.WriteLine(ControlChars.Cr + " Press Enter to continue...")
Console.Read()
End Sub
' Function to open a Telnet session.
Public Function OpenTelnetSession(server As String, Port As Int32) As Boolean
Dim ipAddress As IPAddress = Parse(server)
client.Connect(ipAddress, Port)
stream = Me.client.GetStream()
Return True
End Function
' Button to send a message.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Connect("192.168.8.110", TCP_Order.Text + vbCrLf)
End Sub
' Button to open the Telnet session.
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles OpenTelnet.Click
OpenTelnetSession("192.168.8.110", 10001)
End Sub
' Button to close the Telnet session.
Private Sub CloseTelnet_Click(sender As Object, e As EventArgs) Handles CloseTelnet.Click
End Sub
End Class
That because you are not reading the entire buffer of the response, you are just taking 256 bytes from it:
data = New [Byte](256) {} ' <-
Also you have to free the resource by closing the streams and the TcpClient once you receive the response. Always create the disposable objects by the Using statement to guarantee that.
Synchronous Example
The example below connects to an endpoint in synchronous blocking mode, the caller thread is blocked until a response is returned from the endpoint or an exception is thrown (connection timeout for example.)
Private Function Connect(server As String, port As Integer, Msg As String) As String
Using client As New TcpClient(server, port),
netStream = client.GetStream,
sr = New StreamReader(netStream, Encoding.UTF8)
Dim msgBytes = Encoding.UTF8.GetBytes(Msg)
netStream.Write(msgBytes, 0, msgBytes.Length)
Return sr.ReadToEnd
End Using
End Function
and the caller:
Private Sub TheCaller()
Dim resp As String = Nothing
Try
Dim server = "192.168.8.110"
Dim port = 10001
Dim msg = $"1FFFFFF{ControlChars.CrLf}{ControlChars.CrLf}"
resp = Connect(server, port, msg)
Catch ex As ArgumentNullException
resp = ex.Message
Catch ex As SocketException
resp = ex.SocketErrorCode.ToString
Catch ex As Exception
resp = ex.Message
Finally
If resp IsNot Nothing Then
UpdateStatus(resp)
End If
End Try
End Sub
Asynchronous Example
You may want to use an asynchronous operation since you are developing a WinForms application, and I don't think you want to block the UI thread. Here you need to call the Async methods of the TcpClient and the read/write streams:
Private Async Function ConnectAsync(server As String,
port As Integer, msg As String) As Task(Of String)
Using client As New TcpClient
Await client.ConnectAsync(server, port)
Using netStream = client.GetStream,
sw = New StreamWriter(netStream, Encoding.UTF8) With {.AutoFlush = True },
sr = New StreamReader(netStream, Encoding.UTF8)
Await sw.WriteLineAsync(msg)
Return Await sr.ReadToEndAsync()
End Using
End Using
End Function
and an Async caller:
Private Async Sub TheCaller()
Dim resp As String = Nothing
Try
Dim server = "192.168.8.110"
Dim port = 10001
Dim msg = $"1FFFFFF{ControlChars.CrLf}{ControlChars.CrLf}"
resp = Await ConnectAsync(server, port, msg)
Catch ex As ArgumentNullException
resp = ex.Message
Catch ex As SocketException
resp = ex.SocketErrorCode.ToString
Catch ex As Exception
resp = ex.Message
Finally
If resp IsNot Nothing Then
UpdateStatus(resp)
End If
End Try
End Sub
The UpdateStatus in the code snippets is just a method to append the responses into a TextBox..
Private Sub UpdateStatus(txt As String)
StatusTextBox.AppendText(txt)
StatusTextBox.AppendText(ControlChars.NewLine)
End Sub

Console scrolling too fast in VB.Net

I am learning how to send a message over a TCP/IP connection in Visual Basic between 2 computers linked by an ethernet cable. When I send the message, the console screen scrolls too far down and the received message no longer shows up on the host computer's console window. When I create a For loop that outputs the message several hundred times I can see the message quickly scrolling through the console window, but at the end the window is left black, which I'm thinking means the window continued to scroll.
I am currently inputing a message into the client console and having the listener console output this message.
Here is my host/listener code:
Imports System.Net.Sockets
Imports System
Imports System.IO
Imports System.Net
Imports System.Text
Imports Microsoft.VisualBasic
Module Module1
Sub Main()
'Open listener at port 8
Dim myHost As New TcpListener(8)
myHost.Start()
Console.WriteLine("Waiting for connection")
Dim myClient As TcpClient = myHost.AcceptTcpClient
Console.WriteLine("Connected")
Dim myStream As NetworkStream = myClient.GetStream
Dim bytes(myClient.ReceiveBufferSize) As Byte
Dim receivedMessage As String
myStream.Read(bytes, 0, CInt(myClient.ReceiveBufferSize))
receivedMessage = Encoding.ASCII.GetString(bytes)
Console.WriteLine("Message was: " & receivedMessage)
System.Threading.Thread.Sleep(2000)
Console.ReadLine()
myClient.Close()
myHost.Stop()
End Sub
End Module
Here is my code for the client, the imports are the same as above:
Module Module1
Sub Main()
Dim myClient As New TcpClient
myClient.Connect("My IP", 8) 'Connects to laptop IP on port 8
Dim myStream As NetworkStream = myClient.GetStream()
Dim message As String
message = Console.ReadLine
Console.WriteLine("We are sending the read line")
sendOverIP(message, myStream)
Console.ReadLine()
End Sub
Public Sub sendOverIP(ByVal message As String, ByVal myStream As NetworkStream)
Dim sendBytes As [Byte]() = Encoding.ASCII.GetBytes(message) 'Turns message into ASCII bytes
myStream.Write(sendBytes, 0, sendBytes.Length)
Console.WriteLine("We sent: " & message)
End Sub
End Module
I have a breakpoint at this point in the listener
Console.WriteLine("Message was: " & receivedMessage)
and as soon as I tell it to continue, the console window becomes all black. I'm assuming that it writes the line then continues to scroll. How can I make it so the received message stays on the console output for the listener?
I think it's because you are converting the entire bytes array to a string in the "host/listener". You need to convert just the actual bytes that were received, not the whole buffer:
Dim actualBytes = myStream.Read(bytes, 0, bytes.Length)
receivedMessage = Encoding.ASCII.GetString(bytes, 0, actualBytes)

Is there away to switch from a Worker Thread to the Main (UI) thread?

I apologize in advance if my question is too long-winded. I looked at the question “How to update data in GUI with messages that are being received by a thread of another class?” and it is very close to what I am trying to do but the answer was not detailed enough to be helpful.
I have converted a VB6 app to VB.NET (VS2013). The main function of the app is to send queries to a Linux server and display the results on the calling form. Since the WinSock control no longer exists, I’ve created a class to handle the functions associated with the TcpClient class. I can successfully connect to the server and send and receive data.
The problem is that I have multiple forms that use this class to send query messages to the server. The server responds with data to be displayed on the calling form. When I try to update a control on a form, I get the error "Cross-thread operation not valid: Control x accessed from a thread other than the thread it was created on." I know I’m supposed to use Control.InvokeRequired along with Control.Invoke in order to update controls on the Main/UI thread, but I can’t find a good, complete example in VB. Also, I have over 50 forms with a variety of controls on each form, I really don’t want to write a delegate handler for each control. I should also mention that the concept of threads and delegates is very new to me. I have been reading everything I can find on this subject for the past week or two, but I’m still stuck!
Is there some way to just switch back to the Main Thread? If not, is there a way I can use Control.Invoke just once to cover a multitude of controls?
I tried starting a thread just after connecting before I start sending and receiving data, but netStream.BeginRead starts its own thread once the callback function fires. I also tried using Read instead of BeginRead. It did not work well if there was a large amount of data in the response, BeginRead handled things better. I feel like Dorothy stuck in Oz, I just want to get home to the main thread!
Thanks in advance for any help you can provide.
Option Explicit On
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Friend Class ATISTcpClient
Public Event Receive(ByVal data As String)
Private Shared WithEvents oRlogin As TcpClient
Private netStream As NetworkStream
Private BUFFER_SIZE As Integer = 8192
Private DataBuffer(BUFFER_SIZE) As Byte
Public Sub Connect()
Try
oRlogin = New Net.Sockets.TcpClient
Dim localIP As IPAddress = IPAddress.Parse(myIPAddress)
Dim localPrt As Int16 = myLocalPort
Dim ipLocalEndPoint As New IPEndPoint(localIP, localPrt)
oRlogin = New TcpClient(ipLocalEndPoint)
oRlogin.NoDelay = True
oRlogin.Connect(RemoteHost, RemotePort)
Catch e As ArgumentNullException
Debug.Print("ArgumentNullException: {0}", e)
Catch e As Net.Sockets.SocketException
Debug.Print("SocketException: {0}", e)
End Try
If oRlogin.Connected() Then
netStream = oRlogin.GetStream
If netStream.CanRead Then
netStream.BeginRead(DataBuffer, 0, BUFFER_SIZE, _
AddressOf DataArrival, DataBuffer)
End If
Send(vbNullChar)
Send(User & vbNullChar)
Send(User & vbNullChar)
Send(Term & vbNullChar)
End If
End Sub
Public Sub Send(newData As String)
On Error GoTo send_err
If netStream.CanWrite Then
Dim sendBytes As [Byte]() = Encoding.UTF8.GetBytes(newData)
netStream.Write(sendBytes, 0, sendBytes.Length)
End If
Exit Sub
send_err:
Debug.Print("Error in Send: " & Err.Number & " " & Err.Description)
End Sub
Private Sub DataArrival(ByVal dr As IAsyncResult)
'This is where it switches to a WorkerThread. It never switches back!
On Error GoTo dataArrival_err
Dim myReadBuffer(BUFFER_SIZE) As Byte
Dim myData As String = ""
Dim numberOfBytesRead As Integer = 0
numberOfBytesRead = netStream.EndRead(dr)
myReadBuffer = DataBuffer
myData = myData & Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)
Do While netStream.DataAvailable
numberOfBytesRead = netStream.Read(myReadBuffer, 0, myReadBuffer.Length)
myData = myData & Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)
Loop
'Send data back to calling form
RaiseEvent Receive(myData)
'Start reading again in case we don‘t have the entire response yet
If netStream.CanRead Then
netStream.BeginRead(DataBuffer, 0,BUFFER_SIZE,AddressOf DataArrival,DataBuffer)
End If
Exit Sub
dataArrival_err:
Debug.Print("Error in DataArrival: " & err.Number & err.Description)
End Sub
Instead of using delegates one could use anonymous methods.
Singleline:
uicontrol.Window.Invoke(Sub() ...)
Multiline:
uicontrol.Window.Invoke(
Sub()
...
End Sub
)
If you don't want to pass an UI control every time you need to invoke, create a custom application startup object.
Friend NotInheritable Class Program
Private Sub New()
End Sub
Public Shared ReadOnly Property Window() As Form
Get
Return Program.m_window
End Get
End Property
<STAThread()> _
Friend Shared Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
Dim window As New Form1()
Program.m_window = window
Application.Run(window)
End Sub
Private Shared m_window As Form
End Class
Now, you'll always have access to the main form of the UI thread.
Friend Class Test
Public Event Message(text As String)
Public Sub Run()
Program.Window.Invoke(Sub() RaiseEvent Message("Hello!"))
End Sub
End Class
In the following sample code, notice that the Asynchronous - Unsafe run will throw a Cross-thread exception.
Imports System.Threading
Imports System.Threading.Tasks
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.cbOptions = New ComboBox() With {.TabIndex = 0, .Dock = DockStyle.Top, .DropDownStyle = ComboBoxStyle.DropDownList} : Me.cbOptions.Items.AddRange({"Asynchronous", "Synchronous"}) : Me.cbOptions.SelectedItem = "Asynchronous"
Me.btnRunSafe = New Button() With {.TabIndex = 1, .Dock = DockStyle.Top, .Text = "Run safe!", .Height = 30}
Me.btnRunUnsafe = New Button() With {.TabIndex = 2, .Dock = DockStyle.Top, .Text = "Run unsafe!", .Height = 30}
Me.tbOutput = New RichTextBox() With {.TabIndex = 3, .Dock = DockStyle.Fill}
Me.Controls.AddRange({Me.tbOutput, Me.btnRunUnsafe, Me.btnRunSafe, Me.cbOptions})
Me.testInstance = New Test()
End Sub
Private Sub _ButtonRunSafeClicked(s As Object, e As EventArgs) Handles btnRunSafe.Click
Dim mode As String = CStr(Me.cbOptions.SelectedItem)
If (mode = "Synchronous") Then
Me.testInstance.RunSafe(mode)
Else 'If (mode = "Asynchronous") Then
Task.Factory.StartNew(Sub() Me.testInstance.RunSafe(mode))
End If
End Sub
Private Sub _ButtonRunUnsafeClicked(s As Object, e As EventArgs) Handles btnRunUnsafe.Click
Dim mode As String = CStr(Me.cbOptions.SelectedItem)
If (mode = "Synchronous") Then
Me.testInstance.RunUnsafe(mode)
Else 'If (mode = "Asynchronous") Then
Task.Factory.StartNew(Sub() Me.testInstance.RunUnsafe(mode))
End If
End Sub
Private Sub TestMessageReceived(text As String) Handles testInstance.Message
Me.tbOutput.Text = (text & Environment.NewLine & Me.tbOutput.Text)
End Sub
Private WithEvents btnRunSafe As Button
Private WithEvents btnRunUnsafe As Button
Private WithEvents tbOutput As RichTextBox
Private WithEvents cbOptions As ComboBox
Private WithEvents testInstance As Test
Friend Class Test
Public Event Message(text As String)
Public Sub RunSafe(mode As String)
'Do some work:
Thread.Sleep(2000)
'Notify any listeners:
Program.Window.Invoke(Sub() RaiseEvent Message(String.Format("Safe ({0}) # {1}", mode, Date.Now)))
End Sub
Public Sub RunUnsafe(mode As String)
'Do some work:
Thread.Sleep(2000)
'Notify any listeners:
RaiseEvent Message(String.Format("Unsafe ({0}) # {1}", mode, Date.Now))
End Sub
End Class
End Class
Thank you to those who took the time to make suggestions. I found a solution. Though it may not be the preferred solution, it works beautifully. I simply added MSWINSCK.OCX to my toolbar, and use it as a COM/ActiveX component. The AxMSWinsockLib.AxWinsock control includes a DataArrival event, and it stays in the Main thread when the data arrives.
The most interesting thing is, if you right click on AxMSWinsockLib.DMSWinsockControlEvents_DataArrivalEvent and choose Go To Definition, the object browser shows the functions and delegate subs to handle the asynchronous read and the necessary delegates to handle BeginInvoke, EndInvoke, etc. It appears MicroSoft has already done the hard stuff that I did not have the time or experience to figure out on my own!