In general all asynchronous IO operations should be performed in own threads to avoid blocking the application. Otherwise you will have lags in the GUI or the GUI would be blocked completely in the worst case.
For your case you would need at least one thread for receiving replies and transmitting the queued commands.
The receiving thread should buffer data until a full response has been received. How to do this depends on the data (e.g. line feed with text data or after receiving the number of bytes that has been announced in a protocol header).
Once a full response has been received, this is signaled to other threads. In your case this will be the main (GUI) thread to show the response and the transmit queue to enable sending of the next command. Note that passing data from a thread to GUI elements requires special treatment.
The transmit thread can simply send using a blocking call because it has to wait anyway. After sending it has to wait for the response received signal.
You can put sending and receiving into one or two threads.
Pseudo code for receiving (assuming text data):
while (!KillEvent)
{
do
{
rxChar = ReceiveChar();
Buffer += rxChar;
}
while (rxChar != '\n');
}
In the transmit thread, after sending a command call the above or wait for the event when receiving is done in an own thread.
Using threads and events you avoid calling
sleep
which should be generally avoided and ensure that no system time is wasted.