|
-
Aug 9th, 2012, 02:08 PM
#1
Thread Starter
Lively Member
Timer in loop
As per the code below, this app sends a message via TCP sockets and waits for a reply. It should timeout after 3 seconds (the interval set for tmrSend) or after the reply arrives.
The problem is that tmrSend.Start does not seem to be doing anything. The executing thread is tied up in this loop, and it becomes and endless loop if an acknowledgement is not sent, because the bElapsed variable is never set to true since the tmrSend_Tick event does not fire.
Code:
Private Sub tmrSend_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles tmrSend.Tick
bElapsed = True
End Sub
Private Sub SendData(sMsg as string)
Try
Dim tcp As New TcpClient
TCP.Connect(curLab.IPAddress, curLab.IPPort)
TCP.NoDelay = True
Dim stream As NetworkStream = TCP.GetStream()
Dim data As [Byte]()
'Convert sMsg to byte array Data to send to stream
data = StrToByteArray(sMsg)
stream.Write(data, 0, data.Length)
Thread.Sleep(0)
If curLab.WaitforACKnowledgement Then
tmrSend.Enabled = True
tmrSend.Start()
bElapsed = False
sReply = ""
Do While bElapsed = False Or stream.DataAvailable = True
If bElapsed Then Exit Do
If stream.DataAvailable Then
Dim lenReply As Integer, iOffset As Integer, iSize As Integer
iSize = 1024
Dim strReply(iSize) As Byte
lenReply = 1
Do Until lenReply = 0
lenReply = stream.Read(strReply, iOffset, iSize)
sReply = sReply & ByteArrayToString(strReply)
If lenReply > iSize Then
iOffset = iOffset + lenReply
Else
Exit Do
End If
Loop
stream.Close()
Exit Do
End If
Thread.Sleep(0)
Loop
End If
tmrSend.Stop()
stream.Close(500)
tcp.Close()
bDataSent = True
end sub
-
Aug 9th, 2012, 04:00 PM
#2
Re: Timer in loop
vb.net Code:
Private Sub tmrSend_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles tmrSend.Tick
bElapsed = True
End Sub
Private Sub SendData(sMsg as string)
Try
Dim tcp As New TcpClient
TCP.Connect(curLab.IPAddress, curLab.IPPort)
TCP.NoDelay = True
Dim stream As NetworkStream = TCP.GetStream()
Dim data As [Byte]()
'Convert sMsg to byte array Data to send to stream
data = StrToByteArray(sMsg)
stream.Write(data, 0, data.Length)
Thread.Sleep(0) 'what does the thread do and why does it need to be stopped?
If curLab.WaitforACKnowledgement Then
tmrSend.Enabled = True
'tmrSend.Start() - started above
bElapsed = False
sReply = ""
Do Until bElapsed = True Or stream.DataAvailable = True
Application.DoEvents
Loop
If stream.DataAvailable Then
Dim lenReply As Integer, iOffset As Integer, iSize As Integer
iSize = 1024
Dim strReply(iSize) As Byte
lenReply = 1
Do Until lenReply = 0
lenReply = stream.Read(strReply, iOffset, iSize)
sReply = sReply & ByteArrayToString(strReply)
If lenReply > iSize Then
iOffset = iOffset + lenReply
Else
Exit Do 'this worries me but it may be alright
End If
Loop
stream.Close()
End If
Thread.Sleep(0) 'didn't we already do this?
End If
tmrSend.Stop() 'I'd prefer this to be done on the timer tick to prevent further calls on it and replaced here with Timer.Enabled=False
stream.Close(500)
tcp.Close()
bDataSent = True
end sub
Last edited by dunfiddlin; Aug 9th, 2012 at 04:20 PM.
-
Aug 9th, 2012, 04:32 PM
#3
Re: Timer in loop
I don't like either answer, though the second one will work. The problem is that you have created a Busy Wait, wherein the thread will spin continuously on that loop waiting for something to change. That spinning will block any event processing, which means that the timer tick events will be on the event queue, but the events will never be processed because the CPU is spending ALL of its time spinning on that loop.
By adding a DoEvents, the process is still a busy wait, but now it will pause each time through and pump all the messages, so the tick event will be processed. It's still a busy wait, though, and busy waits are horrible things. If you watch the CPU usage while that loop is running, you will see that it is absolutely PEGGED! You are maxing out CPU usage, while doing nothing useful at all. This will have negative consequences on every other process running on the system, it will increase power usage, increase system heating, and for what?
The solution is to redesign the process. I haven't worked with TCP in some years, but in this case, the solution could be much like the one I used for UDP. You either want to respond to an event raised by the TCP client when data is received, or if such a thing doesn't exist (it doesn't for UDP, exactly), then you want to use some kind of blocking call such as the WaitForAcknowledgement call that you use earlier. Of course, a blocking call will do just that, block, so you will have to run that in a background thread, or you will be blocking the UI thread and freezing the program. Using a busy wait is not an alternative, because the consequences are too severe.
My usual boring signature: Nothing
 
-
Aug 10th, 2012, 02:18 AM
#4
Re: Timer in loop
Try setting the ReceiveTimeout property on the TcpClient object. This will only help with the start of the data though - it sounds like you want the entire message within three seconds?
Frankly I would always recommend either using the async methods (BeginRead/EndRead) or putting the stream reading code in a background thread. Then your UI thread can start a simple timer that returns after 3 seconds to signal the timeout to the code that is waiting for the complete message (note: that is not the network code!).
-
Aug 10th, 2012, 10:21 AM
#5
Thread Starter
Lively Member
Re: Timer in loop
The thing is, even if on the client end the WaitForAcknowledgment is true, the server is not necessarily going to send one. One-way messages are perfectly acceptable. So if an acknowledgement doesn't come across within 3 seconds, it's OK to stop waiting for one and just go on with the show. Three seconds are more than enough for it to come over, the acknowledgement is a tiny message of less than 100 characters/bytes, most of the time.
The TCPClient doesn't have events. I tried declaring it WithEvents at the top under the form class, but still not seeing the events when I choose it in the list. Also, trying to process an acknowledgment message in a received event for the TCP client is a hassle because then I don't know which outgoing message this acknowledgment pairs to without parsing the whole acknowledgment message.
How would ReceiveTimeout work? With a blocking .Read call regardless of whether there is .DataAvailable?
Please pardon my ignorance here. I still write primarily in VB6 [ducks]
-
Aug 14th, 2012, 10:45 PM
#6
Thread Starter
Lively Member
Re: Timer in loop
Modified relevant code. This seems to be doing the right thing.
The .Read call does not block execution, which is necessary here, so a loop is still used, but the Thread.Sleep (250) before the application.doevents seems to prevent the CPU from getting monopolized by the busy loop.
Does this make sense?
vb.net Code:
If curLab.WaitForACKnowledgment Then tmrSend.Start() bElapsed = False sReply = "" If stream.CanRead Then Dim lenReply As Integer, iOffset As Integer, iSize As Integer iSize = 1024 Dim strReply(iSize) As Byte Do Until bElapsed Or lenReply > 0 Thread.Sleep(250) Application.DoEvents() lenReply = stream.Read(strReply, iOffset, iSize) sReply = sReply & ByteArrayToString(strReply) If lenReply > iSize Then iOffset = iOffset + lenReply ElseIf lenReply > 0 And lenReply <= iSize Then Exit Do End If Loop End If End If tmrSend.Stop() stream.Close(500) tcp.Close() bDataSent = True
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|