Qt串口QSerialPort的多线程 及 QSerialPort的waitForReadyRead函数的问题

1.QSerialPort的多线程限制

在官方的文档中提到,QSerialPort是不支持跨线程调用。
在这里插入图片描述因此不能在主线程(UI线程)创建QSerialPort,然后传递个指针给子线程,然后在子线程中执行耗时的发送任务。
最好的方式是在子线程里面创建QSerialPort,然后主线程通过信号槽来间接使用(参数还是可以通过QSerialPort的指针来读写)。

1.1.尝试通过复制参数在子线程运行

这里我想偷个懒,我已经在主线程创建了QSerialPort,并且界面相关的东西都做好了,不想改动太大,因此我想了个办法:
在新线程里面建立一个临时对象,复制传递进去的串口参数,执行完就干掉他。

1.1.1.操作原理

把传递进来的、主线程的QSerialPort给close掉,相当于取消占用。然后在子线程中根据这个串口的配置,重新打开一个QSerialPort对象,并执行写操作。

void MainWindow::on_pushButton_writeFlash_clicked()
{
//    mHexfileReader->writeToDevice(mPort);

   //让writeToDevice函数在子线程中执行
   QMetaObject::invokeMethod(mHexfileReader, "writeToDevice", Qt::QueuedConnection, Q_ARG(QSerialPort*, mPort));
}

------------------------------------
//move to子线程
HexFileReader::HexFileReader(QObject *parent) : QObject(parent)
{
    QThread *thread = new QThread();
    this->moveToThread(thread);
    thread->start();
}

void HexFileReader::writeToDevice(QSerialPort *port)
{
    port->close();

    QSerialPort tmpPort;
    tmpPort.setPortName( port->portName()   );
    tmpPort.setBaudRate( port->baudRate()   );
    tmpPort.setParity(   port->parity()     );
    tmpPort.setDataBits( port->dataBits()   );
    tmpPort.setStopBits( port->stopBits()   );

   // connect(&tmpPort, &QSerialPort::readyRead, [&tmpPort](){
   //    qDebug() << "read all:" << tmpPort.readAll();
   // });

    auto ret = tmpPort.open(QSerialPort::ReadWrite);
    qDebug() << "open port:" << ret;
    
    if(tmpPort.isOpen())
    {
        uchar header[] = {0x31, 0xce};
        qDebug() << "write header:" << tmpPort.write((char*)header, 2);
    }

    tmpPort.close();
}

但是发现,串口成功打开,发送也成功( “write header: 2”),但是串口助手那边就是收不到信息。

1.1.2.QSerialPort::write的异步特性

最后通过漫长的调试(取消多线程、把临时变量换成类成员变量、逐步调试等),终于发现了原因:QSerialPort的write函数,其实并不是同步的
也就是说当调用该函数,并取得返回值后,并不是表示数据已经完全发送出去了。
假如在调用完这个函数之后,立马析构掉QSerialPort对象,那么对方很可能会什么也收不到。

知道了原因,那就好解决了:
在执行写操作之后,执行这个,等待写入(发送)完成。

bool QSerialPort::waitForBytesWritten(int msecs = 30000)

2.使用QSerialPort的waitForReadyRead

前面使用了waitForBytesWritten,我们再看看类似的一个函数:waitForReadyRead。

2.1.遇到的问题

Qt的waitForReadyRead函数经过测试,假如使用不当是有问题(Qt5.12.9),无论怎样都是超时返回。就算别人明确发送了数据过来,而且QSerialPort::readyRead信号也发出了,他还是等到超时,再返回超时错误。

2.2.资料查找

别人也遇到这个问题,而且是升级5.12.10就ok。估计是个bug
-----经过测试,Qt5.12.12也还是不行。
-----再经过测试,发现不是版本的问题,而是Qt事件机制的问题 。灵感来自这里

2.3.无意发现

最后发现将串口的readyRead的信号槽连接方式由

    connect(mPort, &QSerialPort::readyRead, [=](){
          qDebug() << mPort->readAll().toHex(' ') << QTime::currentTime();
    });

变成这样就OK了(用Qt5.12.9也可以)。

    connect(mPort, &QSerialPort::readyRead, this, [=](){
          qDebug() << mPort->readAll().toHex(' ') << QTime::currentTime();
    }, Qt::QueuedConnection);

假如说硬要分析的话,可能是因为这个waitForReadyRead会导致当前进程阻塞,然后就算readyRead信号发出了也无法响应。所以我们在连接串口的readyRead时,要用Qt::QueuedConnection,来使readyRead的所有 信号槽组合 能够执行,而不是卡住。
换言之, 假如你完全不连接这个ReadyRead,那么这个waitForReadyRead函数肯定是可以正常工作的。
但是经过测试,无论使用 Qt::DirectConnection 还是 Qt::QueuedConnection,槽函数都是在主线程(串口所在的线程)执行的,所以应该不是什么阻塞线程的问题。所以究竟真正的原因是啥,我也不知道了。

2.4.尝试Qt源码分析

找到他的源码,看到有个 readCompletionOverlapped,从名字来看作用应该是判断read是否完成。所以,猜想一下,这个waitForReadyRead函数,可能在接收ReadyRead信号后,会利用这个readCompletionOverlapped来判断是否需要返回,而这个东西与read有关,因此,是不是说只要我不读,他就正常工作了呢?
在这里插入图片描述结果一试,果然可以。即使你connect了QSerialPort::readyRead信号,只要你不执行 QSerialPort::read()、QSerialPort::readAll()等函数,waitForReadyRead还是可以正常按照预期执行的。

2.5.解决方法总结

总结一下,解决QSerialPort的waitForReadyRead()总是超时返回的办法有两个:
1.在绑定了readyRead()的我们自己写的槽函数中,不要进行读取数据的操作。
2.将readyRead()与我们自己写的槽函数绑定时,使用Qt::QueuedConnection。

2.6.原因分析

使用了Qt::QueuedConnection就可以的原因,我觉得应该是:
通过使用Qt::QueuedConnection,将槽函数放到了连接到该信号的 槽函数队列 的比较后的位置,(调用waitForReadyRead时,可能里面也执行了类似信号槽的绑定,而且这个绑定应该是 Qt::DirectConnection),因此在信号发出之后,先执行waitForReadyRead里面的槽函数,然后再执行我们自己的函数。这样我们的函数就不会影响到 readCompletionOverlapped 这个变量了。
在这里插入图片描述从下面这段测试代码也可以看到,使用了Qt::QueuedConnection的槽函数,即使你是先于其他槽函数与sender创建连接,他也还是在信号发出后在会在较后位置才执行。

在这里插入图片描述

  • 18
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Qt是一种广泛使用的C++跨平台应用程序开发框架,主要用于开发GUI程序。Qt提供了串口通信的相关类和函数,同时也支持多线程开发。Qt串口通信类中,最重要的类是QSerialPort,它提供了一系列与串口通信相关的函数,包括打开串口、设置串口参数、读取数据、写入数据等。而对于多线程开发,在Qt中通常使用QThread类来创建线程。在串口通信多线程开发中,可以使用QThread类创建一个新的线程来处理串口通信,从而提高程序的并发性和稳定性。可以使用Qt的信号和槽机制来实现不同线程之间的通讯和数据传递。同时,在多线程开发中需要注意线程的同步和互斥,以避免多线程访问同一资源造成的冲突和数据损坏。 Qt串口多线程开发的源码实现需要先创建一个串口通信类,然后继承QThread类创建一个新的线程,在该线程中调用串口通信类的函数进行通讯。在实现过程中需要使用信号和槽机制来实现不同线程之间的通讯和数据传递,同时也需要考虑线程的同步和互斥问题,以避免多线程访问同一资源造成的冲突和数据损坏。 总之,Qt提供了非常完善的串口通信多线程开发的支持,并且在实现过程中也比较简单,只需要熟悉Qt的相关类和函数即可。同时,在开发过程中也需要遵循一些基本的原则,如线程安全、代码可读性等,以确保程序的质量和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值