Results 1 to 6 of 6

Thread: Timer in loop

  1. #1
    Lively Member
    Join Date
    Oct 07
    Posts
    126

    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

  2. #2
    PowerPoster dunfiddlin's Avatar
    Join Date
    Jun 12
    Posts
    5,473

    Re: Timer in loop

    vb.net Code:
    1. Private Sub tmrSend_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles tmrSend.Tick
    2.         bElapsed = True
    3.     End Sub
    4.  
    5. Private Sub SendData(sMsg as string)  
    6.                      
    7. Try
    8.  
    9.     Dim tcp As New TcpClient
    10.  
    11.     TCP.Connect(curLab.IPAddress, curLab.IPPort)
    12.  
    13.     TCP.NoDelay = True
    14.  
    15.     Dim stream As NetworkStream = TCP.GetStream()
    16.  
    17.     Dim data As [Byte]()
    18.     'Convert sMsg to byte array Data to send to stream
    19.     data = StrToByteArray(sMsg)
    20.  
    21.     stream.Write(data, 0, data.Length)
    22.  
    23.     Thread.Sleep(0) 'what does the thread do and why does it need to be stopped?
    24.  
    25.     If curLab.WaitforACKnowledgement Then
    26.  
    27.          tmrSend.Enabled = True
    28.  
    29.          'tmrSend.Start() - started above
    30.  
    31.          bElapsed = False
    32.  
    33.          sReply = ""
    34.  
    35.          Do Until bElapsed = True Or stream.DataAvailable = True
    36.          Application.DoEvents
    37.          Loop
    38.  
    39.                
    40.  
    41.                If stream.DataAvailable Then
    42.  
    43.                       Dim lenReply As Integer, iOffset As Integer, iSize As Integer
    44.  
    45.                       iSize = 1024
    46.  
    47.                       Dim strReply(iSize) As Byte
    48.  
    49.                       lenReply = 1
    50.  
    51.                       Do Until lenReply = 0
    52.                               lenReply = stream.Read(strReply, iOffset, iSize)
    53.                               sReply = sReply & ByteArrayToString(strReply)
    54.                               If lenReply > iSize Then
    55.                                         iOffset = iOffset + lenReply
    56.                               Else
    57.                                         Exit Do 'this worries me but it may be alright
    58.                               End If
    59.                       Loop
    60.                      
    61.  
    62.             stream.Close()
    63.  
    64.            
    65.  
    66.           End If
    67.  
    68.           Thread.Sleep(0) 'didn't we already do this?
    69.  
    70.      
    71.  
    72.     End If
    73.  
    74. 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
    75.  
    76. stream.Close(500)
    77.  
    78. tcp.Close()
    79.  
    80. bDataSent = True
    81. end sub
    Last edited by dunfiddlin; Aug 9th, 2012 at 04:20 PM.

  3. #3
    Loquacious User Shaggy Hiker's Avatar
    Join Date
    Aug 02
    Location
    Idaho
    Posts
    20,390

    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

  4. #4
    Frenzied Member Evil_Giraffe's Avatar
    Join Date
    Aug 02
    Location
    Suffolk, UK
    Posts
    1,878

    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!).

  5. #5
    Lively Member
    Join Date
    Oct 07
    Posts
    126

    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]

  6. #6
    Lively Member
    Join Date
    Oct 07
    Posts
    126

    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:
    1. If curLab.WaitForACKnowledgment Then
    2.  
    3.         tmrSend.Start()
    4.  
    5.         bElapsed = False
    6.  
    7.         sReply = ""
    8.  
    9.         If stream.CanRead Then
    10.  
    11.                  Dim lenReply As Integer, iOffset As Integer, iSize As Integer
    12.  
    13.                  iSize = 1024
    14.  
    15.                  Dim strReply(iSize) As Byte
    16.  
    17.                  Do Until bElapsed Or lenReply > 0
    18.  
    19.                          Thread.Sleep(250)
    20.  
    21.                          Application.DoEvents()
    22.  
    23.                          lenReply = stream.Read(strReply, iOffset, iSize)
    24.                          sReply = sReply & ByteArrayToString(strReply)
    25.  
    26.                          If lenReply > iSize Then
    27.                                  iOffset = iOffset + lenReply
    28.                          ElseIf lenReply > 0 And lenReply <= iSize Then
    29.                                  Exit Do
    30.                          End If
    31.  
    32.                  Loop
    33.  
    34.            End If
    35.  
    36. End If
    37.  
    38. tmrSend.Stop()
    39.  
    40. stream.Close(500)
    41.  
    42. tcp.Close()
    43.  
    44. 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
  •