简体   繁体   中英

C++ Threading Pattern for Qt serial port

My aim is to receive messages from a serial device without blocking the main thread (GUI) and to try to separate the platform-dependent logic (GUI and serial port) from the business logic (processing the messages) for ease of porting to other platforms

Context: I'm using Qt, and the QtSerialPort module. The message protocol is simple, 0xff is used to end each message.

I've found 4 solutions so far:

Method 1:

  1. Using one thread to read a serial port and fill a buffer

  2. Using another thread to read the buffer, extract valid messages (into another buffer? not sure how this will work yet)

  3. Using yet another thread to parse the messages

Method 2:

  1. Using one thread to read a serial port, and extract valid messages into a buffer

  2. Using another thread to parse the messages

Method 3:

  1. Using one thread to read a serial port, extract a valid message, and block till that message is processed, making use of QtSerialPort's internal read buffer to buffer incoming data

Method 4:

  1. Using the main thread to asynchronously read serial port, extract a valid message, and for each message, spawn a new thread to process them

Methods 1,2 and 3 differ by the number of threads the general workload is split up into, though I don't know which is best.

I'm currently using method 4, which is horribly inefficient and doesn't work well on lower-end computers, due to the enormous number of threads being spawned, and every time I move or interact with the GUI, serial communication halts. Spawning a thread for each message also makes the order of the messages non-deterministic, which hasn't been a major problem so far...

Are there other methods, what are the pros (if any) and cons of each, and which is the best to use? Thanks!

EDIT: A problem with processing messages in the main thread is that interacting with GUI (even moving the window) would block the message processing function. Is there any way around this?

I think there are two main advantages that you can obtain by using multithreading:

  1. Avoiding poor GUI performance due to the GUI-handling routines being held off by the serial port processing routine
  2. (perhaps more important) Avoid loss of serial data caused by buffer overflow when the GUI routines hold off the serial-data-reading routine for too long.

You should only need to spawn a single thread. Just have that thread read data from the serial port as it comes in (by connecting the QSerialPort's readyRead() signal to a slot that calls read() on the QSerialPort object), and then emit a signal (with a QByteArray argument) whenever it wants to send some serial data to the GUI. Your main/GUI thread can receive the data via a QueuedConnection that will not block either the serial-thread or the main/GUI thread.

That's pretty much all there is to it; the only other thing to worry about is a clean shutdown. Be sure to have another cross-thread signal/slot connection to the QThread's quit() slot, so that when it's time to quit, you can emit that signal and then call wait() on the QThread to wait for it to respond by going away. Once wait() has returned you can safely delete the QThread object.

You can avoid additional threads at all by simply relying on Qt event loop (so far the main thread, the one also handling the GUI to be clear, will be blocked only when a message is actually received by the serial port).

Otherwise if you want to completely handle serial port in a dedicated thread, then the solution is to implement a class deriving from QThread and then override the run() function with something like this:

void MyClass::run()
{
     QSerialPort port;

     // ... serial port initialization here

     // Connect signals/slots
     connect(&port, SIGNAL(readyRead()), this, SLOT(readData()));

     port.open();

     // Start a new message loop on this thread
     exec();
}

Where readData is a function implemented in MyClass for handling the received data. Since port is owned by the new thread (being created in run() ) then its events will be handled by the thread itself (in a completely independent manner with respect to the main thread).

If you want at some point communicate something to the main thread (eg: you received something on serial which should cause a change in your GUI) then you can still use Qt's signals/slots. Simply implement a signal on MyClass and implement a slot on an object handled by the main thread (eg: your main form): then simply connect the signal for MyClass and the slot on your main form and you're done: signals/slots is THE solution for cross-thread communication in Qt.

You could also avoid using any (additional) threads and take advantage of Qt event loop . Read about events , QioDevice ; then Qt would pass your device file descriptor to its multiplexing loop (eg to poll(2) ....); probably QSocketNotifier should work (on Posix) on a non-socket file descriptor like a serial device.

Details are probably OS specific

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM