Hidden buffer in TCPsocket?

The test equipment vendor was able to steer me to a workable solution. It was all a timing problem, where I could get Xojo to postpone the main thread to service the TCP socket. The solution they found was to use the Xojo command app.doevents, which apparently halts the operation of the main thread to complete the communications transfer. It was key, as the main thread depended upon the return data from the test equipment before continuing. I could find no combination of threads, timers, and juggling of priorities to solve the problem.

do NOT use App.DoEvents in desktop applications it will cause major issues if used incorrectly.
Don’t expect this to be a solution.

Instead use the socket async event based. Create a socket and use the events, do not use .LookAhead or . Read[All] in any place other than the event(s) or methods called by the events.

I could find no combination of threads, timers, and juggling of priorities to solve the problem.
This is because you should not use a combination of those, instead just use the event (DataAvailable) to parse your data. if there is not enough data, then just wait or call .Poll (in a timer) if you want the data to be read faster.

Event-based tcpsockets run on the main thread they will cause no issues if used properly.

2 Likes

This does sound like a tricky sequence of events to get right - but DoEvents should ONLY be used in Console applications. This can’t be stressed enough: do NOT USE DoEvents in Desktop applications.

The reasons are obscure, related to the way Xojo orders and processes events internally, but the results are that you will (eventually) have mysterious “bugs” that are impossible to track down and don’t always occur. You’ll end up restructuring your app to remove DoEvents at that point anyhow, so you may as well do it right in the first place. :wink:

However, if your app is a Console application, DoEvents is in fact critical to making it work correctly and you should read up on how to do this properly.

Eric, I read about this in the description, however I have had this post up for weeks and have not gotten a clue as to how to suspend the main thread while a bi-directional communication takes place. This is a requirement for my application. Suggestions for using timers, threads, and working with priorities slow the program without solving the problem. Can you point me to a method for solving this problem properly?

I did try the various methods I mentioned one at a time. Dataavailable, for example, does not halt the main thread while waiting for the event to fire. If I put the main thread in a loop to keep it busy until the event releases it, it hangs because the tight loop in the main thread won’t permit the event to fire. I spent weeks trying to get the program to follow this strict time sequence without success. The purpose of the application is to communicate with two pieces of test equipment to gather data and to evaluate that data. To do that, there must be a strict sequence of events involving over 50 communications following a strict timing sequence as instructed by the main thread. The main thread must get the resulting data from each communication before continuing, but I have yet to find a method of accomplishing this other than the apps.doevents command, when I use it in “write” and “read” methods. There should be a proper way to do it, but I have not been directed to it.

If you are waiting for things to happen, why is your main thread doing anything at all?

I am using the main thread to perform different user-selected test routines from a UI. For each routine, it issues commands and gathers data from external test equipment in an orchestrated series of method calls, and uses the accumulated data to make decisions and to evaluate the data, but it has to all be done sequentially. As for as the user is concerned, there is no input while each test routine is running.

Sounds like an ideal situation for a thread, to me. If a routine needs to do I/O with external equipment, how, having issued some command to that equipment, is it supposed to wait for the response from the remote end. If you just loop the main thread, that’s the end of any user interaction with the app. How does the user abort the process if there is a communications hickup and the ends get out of sync?

In my case my app uses threads and can communicate with a number of remote hosts at the same time with no thread interfering with another. A thread sends a message, expecting a response, and after the send it starts a timer, pauses itself and waits to be resumed by an event - data available? good, get the data and proceed - error? OK log a message and give up - timeout? OK, issue another message and give up. Meanwhile the entire app remains responsive and the user can do other things at the same time.

Do you have desktop application or console application? @Wayne_Miller

If you’re asking me, then it’s a Desktop app.

I think you misunderstood what the suggestions gave. Xojo already has a main thread in desktop applications, there is no need to create a loop. In console applications things are way different.

You need to create a subclass of a tcpsocket or serialconnection, create a property on a window or the app class for example. Then connect to your device the subclass handles the dataavailable event and xojo (desktop) will run the events if there is data. You can call socket.poll in a timer to get this data be handled faster (when there is data in the system buffer.

This is Async or event based. The polling is optional, even threads would make this much slower.

I have a desktop application. I created an instance of a TCPsocket on the main window (if that is what I should be doing). When I try to use the dataavailable or timer to handle an event, the main does not stop for it. The event doesn’t fire until whatever is executing from the main window is finished. I know I’m missing something, but that’s what I get.

If I understand you correctly, my problem is somewhat different. While my routine is running, the user will have no input. The communications to the equipment is through a fixed routine that the user initiates. From that point, each piece of communication represents the acquisition of a bit of data that the fixed routine must get back before it can continue. This timing is critical. Multiple threads would not help, as each bit of data must be received before the next bit is to be called for.

Sounds like what you need is a state machine.

So just to be very clear here


Sockets, Timers and UI always run on the main thread, so yes blocking the main thread with a loop is going to cause all of this stuff to hang.

As has been mentioned, DoEvents will appear to work on the short term, but you will inevitably run into issues down the road which will require you to remove it unless you are very, very careful.

If you have things that need to run in a certain order, think about making an enum (or a series of numerical constants) that lists the states in the order that you need them done and then use a Select Case in your processing code to advance to the next state as you go. Something like:

Select Case state
Case States.Step1
  // do step 1 things
  // send query for step 2
  State = States.Step2
Case States.Step2
  // do step 2 things
  // and so on...
End Select

This way you can keep everything responsive and still in sync. Just remember to display a modal dialog or disable the UI that users shouldn’t be fiddling with while it runs.

1 Like

After reading the thread, it looks to me that you have two issues going on. i) Not quite understanding how the Xojo framework works. ii) Poor message processsing, SCPI I presume.

i. You have two choices. Event driven comms using DataAvailable. Sequential comms using the .Poll method. Both have their challenges and compromises. Event driven comms would be suitable for sending commands to the instrument in arbitrary order. Sequential comms is suitable for the test procedure use case, where commands and responses are processed in a set order.

The .Poll method is to a Xojo Socket what DoEvents is to a Xojo application. When you set Socket properties and call Socket methods, you change the state of the socket in software but very little happens until your code yields to the Xojo framework, allowing the framework to do it’s stuff. By calling .Poll you instruct the framework to immediately service only the socket, without doing all the other things that DoEvents does. To be clear, a call to .DoEvents is an implicit call to .Poll.

Caveats. Code is off the top of my head and the last time I did anything with SCPI Xojo was called RealBasic.

Button.Action
  TCPSocket1.Connect
  Do
  Loop Until TCPSocket1.IsConnected
  
  MsgBox "This line is never executed"
End

Button.Action
  TCPSocket1.Connect
  Do
    TCPSocket1.Poll
  Loop Until TCPSocket1.IsConnected

  MsgBox "Connected"
End

ii. Connecting is the easy bit. For the the next step you need to actually understand the protocol you are implementing. Before reading or writing anything to the network you need to know at which end the conversation starts and how individual messages are delineated within the protocol stream. A quick look at the Copper Mountain docs suggest your instrument is talking SCPI (scippy). SCPI messages are terminated by the ‘\n’ newline symbol, which is ChrB(10) in Xojo.

Key point. Neither TCP nor the OS nor Xojo, has the feintest clue what SCPI looks like. This means you must provide the code to handle an input buffer that may contain multiple and/or partial messages. The implication is that there are very few reasons to ever call .ReadAll when processing application messages sent over TCP. One of the few reasons to use ReadAll is to clear garbage from the input buffer before initiating a comms sequence. Ideally there shouldn’t be garbage in the input buffer. Calling .ReadAll inappropriately will almost certainly break the protocol message stream.

For the application protocol stream to remain intact code must read messages from the buffer atomically and completely.

//read only complete messages
Var nextMessage as string
Var eom as integer //end of message position

eom = Socket1.LookAhead.InStrB(ChrB(10))
While eom > 0
  nextMessage = Socket1.Read(eom)
  //do something with nextMessage
  eom = Socket1.InStrB(ChrB(10))
Wend

Put this all together and we can quickly write a proof of concept to query the scpi IDeNtity command using sequential comms. As the code uses the .Poll method it is sensible to include a timeout mechanism as the UI will lock up while the request/response completes. For requests with longer wait times you might set the UI wth a please wait message before initiating the comms sequence.

Button1.Action
  const instrumentAddress = "10.2.3.4"
  const instrumentPort = 5025
  var scpiResponse() as string
  
  TcpSocket1.Address = instrumentAddress
  TcpSocket1.port = instrumentPort
  
  if not doConnect then
    MsgBox "Failed to connect to instrument"
    return
  end if

  //clear the input buffer
  call socket.ReadAll

  If not doIDN(TcpSocket1, scpiResponse()) then
    MsgBox "Instrument failed to respond!"
    return
  end if
  
  if scpiResponse.Count <> 3 then
    msgbox "Invalid response from instrument!"
    return
  end if  

  var message as string
  message = "Manufacturer: " + scpiResponse(0) + endofline _
    "Model: " + scpiResponse(1) + endofline _
    "Serial #: " + scpiResponse(2) + endofline
  MsgBox message
end

function milliseconds() as integer
  // short duration timing in milliseconds
  return microseconds / 1000
end

function doConnect(socket as TcpSocket) as boolean
  const tmTimeOut = 2000
  Var tmStart as integer, tmNow as integer, tmElapsed as integer
  
  tmStart = millilseconds
  socket.Connect
  do
    socket.poll
    tmNow = milliseconds
    tmElapsed = tmNow - tmStart 
  loop until (socket.IsConnected) or (tmElapsed > tmTimeOut)
  
  return socket.IsConnected
end

function doIDN(socket as TcpSocket, byref response()) as boolean
  const tmTimeOut = 2000
  Var tmStart as integer, tmNow as integer, tmElapsed as integer
  Var eom as integer, response as string

  if not socket.IsConnected then
    return false
  end if

   socket.Write "*IDN?" + ChrB(10)
   tmStart = milliseconds
   do
     socket.poll
     tmNow = milliseconds
     tmElapsed = tmNow - tmStart
     eom = socket.lookahead.InStrB(ChrB(10))
   loop until (eom > 0) or (tmElapsed > tmTimeOut)

   if tmElapsed > tmTimeOut then
     return false
   end if
 
  response = socket.read(eom).Split(",")
  return true
end

As I said, the other option is DataAvailable but that is a whole different post and would need a state machine, as @Greg_O_Lone says.

Greg, that is a very good idea. I had thought about approaching the problem that way, but never tried it. I thought that there should be a nice way in Xojo to do it without having to completely restructure my program. (sort of like when you tell a good dog to “sic 'em”. The dog stops doing what he’s doing, goes off to sic 'em, then goes back to what he was doing.) which would not be an easy task.

You cautioned most strongly against DoEvents, but went on to say " unless you are “very, very careful.” Perhaps, if I knew how to do that, it might be the way to go. Right now, the program is running well. I only use the DoEvents in two methods: a write to the test equipment and a read to the same equipment. I have added no other event handlers (other than open and close–no timers, no dataavailable, no threads, etc) There are different tasks in the UI, but only one can be selected at a time, and each one consists of a single train of commands. Is there a chance that my simple, straight-line tasks may not encounter a problem with DoEvents?

Matthew, of all the things I tried, I did not try poll. I would like to thank you for your thoughtful an complete response to my problem. As for my first problem, ie not understanding the Xojo, you are absolutely correct. When I first started using REALbasic, I worked with Mr. Ford’s book on it and have been using that approach successfully for years. The framework has mutated and I don’t have a good grasp of it. Is there a good reference text that would help me? The language references within the Xojo environment are cryptic and often misleading or outdated.

Thanks again for your help.

I seriously doubt that it is and as we’ve documented that it should not be used in Desktop projects, I cannot help you with doing that.

Use the state machine as I suggested and it will be much more stable in the long run.

From my experience I only can strongly reconmend „don‘t use App.DoEvents in Desktop apps“. This can‘t be stressed out enough. Even if it works for now, it‘s a perfect „technique“ to get in trouble sooner or later in a part of your app that has nothing to do with your current code.

1 Like