Qt串行端口的C++线程模式

C++ Threading Pattern for Qt serial port

本文关键字:线程 模式 C++ 串行端口 Qt      更新时间:2023-10-16

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

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

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

方法1:

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

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

  3. 使用另一个线程解析消息

方法2:

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

  2. 使用另一个线程解析消息

方法3:

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

方法4:

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

方法1、2和3的不同之处在于一般工作负载划分为的线程数,尽管我不知道哪一个最好。

我目前使用的是方法4,它效率非常低,在低端计算机上运行不好,因为产生了大量的线程,每次我移动或与GUI交互时,串行通信都会停止。为每条消息生成一个线程也会使消息的顺序变得不确定,这到目前为止还不是一个大问题。。。

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

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

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

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

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

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

您可以通过简单地依赖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的信号和主窗体上的插槽,就完成了:signals/sslots是Qt中跨线程通信的解决方案。

您还可以避免使用任何(额外的)线程,并利用Qt事件循环。阅读活动,QioDevice;则Qt将把你的设备文件描述符传递给它的多路复用循环(例如轮询(2)…);QSocketNotificationer可能应该(在Posix上)在非套接字文件描述符上工作,就像串行设备一样。

详细信息可能是操作系统特定的