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