Qt 串口的 C++ 线程模式

Posted

技术标签:

【中文标题】Qt 串口的 C++ 线程模式【英文标题】:C++ Threading Pattern for Qt serial port 【发布时间】:2014-09-05 05:30:21 【问题描述】:

我的目标是在不阻塞主线程 (GUI) 的情况下从串行设备接收消息,并尝试将依赖于平台的逻辑(GUI 和串行端口)与业务逻辑(处理消息)分离以方便移植到其他平台

上下文:我正在使用 Qt 和 QtSerialPort 模块。消息协议很简单,0xff 用于结束每条消息。

到目前为止,我已经找到了 4 个解决方案:

方法一:

    使用一个线程读取串口并填充缓冲区

    使用另一个线程读取缓冲区,提取有效消息(到另一个缓冲区?还不确定这将如何工作)

    使用另一个线程来解析消息

方法二:

    使用一个线程读取串口,并将有效消息提取到缓冲区中

    使用另一个线程解析消息

方法三:

    使用一个线程读取串行端口,提取有效消息并阻塞直到该消息被处理,利用 QtSerialPort 的内部读取缓冲区来缓冲传入数据

方法四:

    使用主线程异步读取串口,提取一条有效消息,并为每条消息生成一个新线程来处理它们

方法 1,2 和 3 的区别在于一般工作负载被分成的线程数,虽然我不知道哪个最好。

我目前正在使用方法 4,该方法效率极低,并且在低端计算机上效果不佳,因为产生了大量线程,并且每次我移动或与 GUI 交互时,串行通信停止。为每条消息生成一个线程也会使消息的顺序不确定,到目前为止这还不是一个主要问题......

还有其他方法吗,每种方法的优缺点是什么(如果有的话),哪种方法最好用?谢谢!

编辑:在主线程中处理消息的一个问题是与 GUI 交互(甚至移动窗口)会阻塞消息处理功能。有没有办法解决这个问题?

【问题讨论】:

【参考方案1】:

我认为使用多线程可以获得两个主要优势:

    避免由于 GUI 处理例程被串行端口处理例程阻止而导致 GUI 性能不佳 (也许更重要)当 GUI 例程延迟串行数据读取例程太久时,避免由于缓冲区溢出导致串行数据丢失。

您应该只需要生成一个线程。只需让该线程从串行端口读取数据(通过将 QSerialPort 的 readyRead() 信号连接到调用 QSerialPort 对象上的 read() 的插槽),然后每当它发出信号(带有 QByteArray 参数)想要向 GUI 发送一些串行数据。您的主/GUI 线程可以通过 QueuedConnection 接收数据,不会阻塞串行线程或主/GUI 线程。

这就是它的全部内容;唯一需要担心的是彻底关机。确保与 QThread 的 quit() 槽有另一个跨线程信号/槽连接,这样当需要退出时,您可以发出该信号,然后在 QThread 上调用 wait() 以等待它响应离开。一旦 wait() 返回,您就可以安全地删除 QThread 对象了。

【讨论】:

问题是处理数据必须在另一个线程中,因为 GUI 可能会阻止它,所以我无法将串行数据发送到 GUI 线程 如果数据处理确实需要立即完成,您可以生成第二个线程专门用于该处理。但是,您是否 100% 确定必须始终毫不拖延地处理数据?如果数据处理延迟几十毫秒,您预计会出现什么问题? 啊,我明白了。在这种情况下,要么直接在串行端口线程中进行所有数据处理,要么(如果处理速度足够慢和/或串行波特率足够快,以至于您担心串行缓冲区可能会溢出,而串行-线程正忙于处理数据)生成第二个线程进行数据处理,并让它直接与串行线程通信(仍然通过 QueuedConnection 信号/插槽)。无论哪种情况,您都将 GUI 线程完全排除在循环之外。【参考方案2】:

您可以通过简单地依赖 Qt 事件循环来完全避免额外的线程(到目前为止,主线程,也就是处理 GUI 的主线程,只有在串行端口实际接收到消息时才会被阻塞)。

否则如果你想在一个专用线程中完全处理串口,那么解决方案是实现一个从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();

其中readData 是在MyClass 中实现的用于处理接收到的数据的函数。由于port 由新线程拥有(在run() 中创建),因此其事件将由线程本身处理(以相对于主线程完全独​​立的方式)。

如果您想在某些时候与主线程通信(例如:您在串行上收到了一些内容,这应该会导致您的 GUI 发生变化),那么您仍然可以使用 Qt 的信号/插槽。只需在MyClass 上实现一个信号并在由主线程处理的对象上实现一个插槽(例如:您的主窗体):然后只需将MyClass 的信号和主窗体上的插槽连接起来,您就完成了:信号/插槽是 Qt 中跨线程通信的THE解决方案。

【讨论】:

【参考方案3】:

您还可以避免使用任何(额外的)线程并利用 Qt event loop。阅读events、QioDevice;然后 Qt 会将您的设备文件描述符传递给它的多路复用循环(例如,传递给poll(2)....);可能QSocketNotifier 应该在非套接字文件描述符(如串行设备)上工作(在 Posix 上)。

细节可能因操作系统而异

【讨论】:

以上是关于Qt 串口的 C++ 线程模式的主要内容,如果未能解决你的问题,请参考以下文章

Qtmodbus之串口模式读操作

怎么判断串口已发送完毕?(qt5, c++)

STM32F4 DMA接收串口定长数据,串口每秒来1000个数据,使用DMA-Normal模式

STM32F4 DMA接收串口定长数据,串口每秒来1000个数据,使用DMA-Normal模式

Qt 串口和线程的简单结合(通过子线程操作串口movetothread)

Qt 中的最佳生产者/消费者线程模式