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
我的目标是从串行设备接收消息而不阻塞主线程(GUI),并尝试将平台相关逻辑(GUI和串行端口)与业务逻辑(处理消息)分开,以便于移植到其他平台
Context: I'm using Qt, and the QtSerialPort module. The message protocol is simple, 0xff is used to end each message.
上下文:我正在使用Qt和QtSerialPort模块。消息协议很简单,0xff用于结束每条消息。
I've found 4 solutions so far:
到目前为止我找到了4个解决方案:
Method 1:
-
Using one thread to read a serial port and fill a buffer
使用一个线程读取串行端口并填充缓冲区
-
Using another thread to read the buffer, extract valid messages (into another buffer? not sure how this will work yet)
使用另一个线程来读取缓冲区,提取有效的消息(进入另一个缓冲区?不知道这将如何工作)
-
Using yet another thread to parse the messages
使用另一个线程来解析消息
Method 2:
-
Using one thread to read a serial port, and extract valid messages into a buffer
使用一个线程读取串行端口,并将有效消息提取到缓冲区中
-
Using another thread to parse the messages
使用另一个线程来解析消息
Method 3:
- 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
使用一个线程读取串行端口,提取有效消息,并阻塞直到该消息被处理,利用QtSerialPort的内部读缓冲区来缓冲传入的数据
Method 4:
- 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.
方法1,2和3的不同之处在于一般工作负载被分成的线程数,尽管我不知道哪个最好。
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...
我目前正在使用方法4,由于产生了大量线程,因此在低端计算机上工作效率非常低并且效果不佳,每次移动或与GUI交互时,串行通信都会停止。为每条消息生成一个线程也会使消息的顺序不确定,这到目前为止还不是一个主要问题......
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?
编辑:在主线程中处理消息的问题是与GUI交互(甚至移动窗口)将阻止消息处理功能。有没有办法解决?
3 个解决方案
#1
4
I think there are two main advantages that you can obtain by using multithreading:
我认为使用多线程可以获得两个主要优点:
- Avoiding poor GUI performance due to the GUI-handling routines being held off by the serial port processing routine
- (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.
由于串行端口处理例程阻止了GUI处理例程,因此避免了较差的GUI性能
(可能更重要)当GUI例程延迟串行数据读取程序太久时,避免因缓冲区溢出而导致串行数据丢失。
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.
您应该只需要生成一个线程。只需让该线程从串口读取数据(通过将QSerialPort的readyRead()信号连接到调用QSerialPort对象上的read()的插槽),然后每当它发出一个信号(带有QByteArray参数)想要将一些串行数据发送到GUI。您的主/ GUI线程可以通过QueuedConnection接收数据,该QueuedConnection不会阻塞串行线程或主/ GUI线程。
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.
这就是它的全部内容;唯一需要担心的是干净关机。确保在QThread的quit()插槽中有另一个跨线程信号/插槽连接,这样当它退出时,你可以发出该信号然后在QThread上调用wait()以等待它响应远。一旦wait()返回,您可以安全地删除QThread对象。
#2
2
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).
您可以通过简单地依赖Qt事件循环来避免额外的线程(到目前为止主线程,也是处理GUI清除的线程,只有在串口实际接收到消息时才会被阻止)。
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:
否则,如果你想在专用线程中完全处理串口,那么解决方案是实现一个从QThread派生的类,然后用这样的方法覆盖run()函数:
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).
其中readData是在MyClass中实现的用于处理接收数据的函数。由于端口由新线程拥有(在run()中创建),因此其事件将由线程本身处理(相对于主线程以完全独立的方式)。
If you want at some point communicate something to the main thread (e.g.: 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 (e.g.: 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.
如果你想在某些时候与主线程进行通信(例如:你在串口上收到一些应该导致GUI更改的东西),那么你仍然可以使用Qt的信号/插槽。只需在MyClass上实现一个信号,并在主线程处理的对象上实现一个插槽(例如:你的主窗体):然后只需连接MyClass的信号和主窗体上的插槽即可完成:信号/插槽是Qt中的跨线程通信解决方案。
#3
0
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 (e.g. to poll(2)....); probably QSocketNotifier should work (on Posix) on a non-socket file descriptor like a serial device.
您还可以避免使用任何(附加)线程并利用Qt事件循环。阅读有关活动的信息,QioDevice;那么Qt会将你的设备文件描述符传递给它的多路复用循环(例如to poll(2)....);可能QSocketNotifier应该在非套接字文件描述符(如串行设备)上工作(在Posix上)。
Details are probably OS specific
细节可能是特定于OS的
#1
4
I think there are two main advantages that you can obtain by using multithreading:
我认为使用多线程可以获得两个主要优点:
- Avoiding poor GUI performance due to the GUI-handling routines being held off by the serial port processing routine
- (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.
由于串行端口处理例程阻止了GUI处理例程,因此避免了较差的GUI性能
(可能更重要)当GUI例程延迟串行数据读取程序太久时,避免因缓冲区溢出而导致串行数据丢失。
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.
您应该只需要生成一个线程。只需让该线程从串口读取数据(通过将QSerialPort的readyRead()信号连接到调用QSerialPort对象上的read()的插槽),然后每当它发出一个信号(带有QByteArray参数)想要将一些串行数据发送到GUI。您的主/ GUI线程可以通过QueuedConnection接收数据,该QueuedConnection不会阻塞串行线程或主/ GUI线程。
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.
这就是它的全部内容;唯一需要担心的是干净关机。确保在QThread的quit()插槽中有另一个跨线程信号/插槽连接,这样当它退出时,你可以发出该信号然后在QThread上调用wait()以等待它响应远。一旦wait()返回,您可以安全地删除QThread对象。
#2
2
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).
您可以通过简单地依赖Qt事件循环来避免额外的线程(到目前为止主线程,也是处理GUI清除的线程,只有在串口实际接收到消息时才会被阻止)。
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:
否则,如果你想在专用线程中完全处理串口,那么解决方案是实现一个从QThread派生的类,然后用这样的方法覆盖run()函数:
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).
其中readData是在MyClass中实现的用于处理接收数据的函数。由于端口由新线程拥有(在run()中创建),因此其事件将由线程本身处理(相对于主线程以完全独立的方式)。
If you want at some point communicate something to the main thread (e.g.: 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 (e.g.: 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.
如果你想在某些时候与主线程进行通信(例如:你在串口上收到一些应该导致GUI更改的东西),那么你仍然可以使用Qt的信号/插槽。只需在MyClass上实现一个信号,并在主线程处理的对象上实现一个插槽(例如:你的主窗体):然后只需连接MyClass的信号和主窗体上的插槽即可完成:信号/插槽是Qt中的跨线程通信解决方案。
#3
0
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 (e.g. to poll(2)....); probably QSocketNotifier should work (on Posix) on a non-socket file descriptor like a serial device.
您还可以避免使用任何(附加)线程并利用Qt事件循环。阅读有关活动的信息,QioDevice;那么Qt会将你的设备文件描述符传递给它的多路复用循环(例如to poll(2)....);可能QSocketNotifier应该在非套接字文件描述符(如串行设备)上工作(在Posix上)。
Details are probably OS specific
细节可能是特定于OS的