QT 使用线程完成串口数据的读写,并把数据传递给主线程,纠正网上例程的普遍错误QObject: Cannot create children for a par

411 阅读3分钟

使用环境:ubuntu18.04, 目标芯片架构 aarch64。产品芯片RK3568

1、之前做串口通信的时候直接在主线程 用了api实现,这种只做个串口助手是OK的,但是在大型项目中这样做会占用主线程的时间,数据量大的时候不确定有多卡顿。

2、看了网上很多资料,没一个写的是对的,主要错在 QSerialPort()在主线程里面创建,因为QIODevice的子类都不应跨线程操作。不然会有这种报错 QObject: Cannot create children for a parent that is in a different thread.

3、花了几天时间,实现开一个线程完成串口数据的读写,并把数据传递给主线程,主线程界面UI不会被阻塞。可以把这个线程完美嵌入到各个大型项目。

4、分享主要思路如下:

4.1、写个类用来操作QSerialPort,类成员里放一个QSerialPort指针,然后把对外交互的函数都写成槽,再写一个creatSerialPort完成指针的实例化和参数初始化。重点: 指针的实例化 要在线程建立后,在线程内实例化。

4.2、然后,在构造这个类的地方,也构造一个QThread,将类对象moveToThread(thread),thread->start(),将thread的started信号绑定到obj的init函数上,把QSerialPort的readyRead信号绑到这个obj的槽上。槽里调用read,然后把读的结果用信号emit到外部。写操作,在obj里提供一个write写槽函数内直接调用串口类的write。外部使用时,emit一个信号,该信号触发obj的write槽函数。

4.3 需要在子线程分配的资源,比如QSerialPort,全部在该object的某个槽函数(如init)中进行,将该槽函数绑定到线程的started信号上
4.4、程序退出时,不要直接delete object,因为那个对象不处于子线程。在需要退出时,执行object的deleteLater函数(直接调用或通过信号槽触发均可),这样就会由它所属的线程负责delete这个object。然后将object的destroyed信号,绑定到线程的quit槽上,将线程的finished信号绑定到QThread对象的deleteLater槽上。这样,销毁流程就是->子线程删除object->线程停止->线程对象销毁
4.5、线程对象,以及move到线程里的对象,都不要设置parent
4.6、Qt 4.8之后,可以把线程的finish信号直接绑到对象的deleteLater上,QThread会保证在子线程中删除这些对象。

5、部分代码如下,供大家参考 (完整代码已嵌入目前项目,已测试稳定,不崩溃,因为花了挺久时间,不无偿公开。VX:JUESHIZIYE 思路可以无偿公开:如上面第四条描述)

serialportservice.h

class SerialPortService : public QObject
{
    Q_OBJECT
public:
    explicit SerialPortService(QObject *parent = nullptr);
    ~SerialPortService();

    void setSerialPara(QString name,QSerialPort::BaudRate br,QSerialPort::DataBits dn,
         QSerialPort::StopBits sn,QSerialPort::Parity pr,QSerialPort::FlowControl fl);
public slots:
    void creatSerialThread();
    void creatSerialPort();

    void readData();  //通过串口读数据
    void writeData(QByteArray data); //通过串口写数据

    void destorySerialThread();
    void closeSerialThread();

signals:

  //发送数据到其它线程
  void sendData(QByteArray data);

private:
//  QThread *serialThread;
  QSerialPort *serialPort;


  QString portName;
  QSerialPort::BaudRate baudRate;
  QSerialPort::DataBits dlen;
  QSerialPort::StopBits slen;
  QSerialPort::Parity par;
  QSerialPort::FlowControl fctrl;
};

serialtestpage.cpp ​

#include "serialtestpage.h"

SerialTestPage::SerialTestPage(QWidget *parent) : QWidget(parent)
{
    setAttribute(Qt::WA_DeleteOnClose);
    dataInit();
    layoutInit();
    scanSerialPort();

    /* 波特率初始化 */
    baudRateItemInit();
    /* 数据位项初始化 */
    dataBitsItemInit();
    /* 检验位项初始化 */
    parityItemInit();
    /* 停止位项初始化 */
    stopBitsItemInit();
    /*流控方式*/
    flowControlInit();

    initSerialPortService();
}
void SerialTestPage::receiveSerialData(QByteArray data)
{
   qDebug() << "SerialTestPage::receiveSerialData currentThreadId:" << 
   QThread::currentThreadId();
   if(data.size() >= 1){
       qDebug()<<"receiveSerialData tmpdata = " << data;
   }
   displaySerialData(data);
}

void SerialTestPage::threadFinished()
{
    qDebug()<<"SerialTestPage threadFinished"<<QThread::currentThreadId();
//    serialPortService->deleteLater();

}

void SerialTestPage::serialDestory()
{
    qDebug()<<"SerialTestPage serialDestory"<<QThread::currentThreadId();
    serialThread->quit();
    serialThread->wait();
}

void SerialTestPage::openSerialPortPushButtonClicked()
{
    if(pushButton[1]->text() == "打开串口"){
        if(comboBox[0]->currentText().isEmpty()){
            qDebug() << "isEmpty ********";
            QMessageBox::about(NULL, "错误","为空!可能串口已经被占用!");
            return;
        }

        if(comboBox[0]->currentText().isNull()){
            qDebug() << "isNull ********";
            QMessageBox::about(NULL, "错误","isNull!可能串口已经被占用!");
            return;
        }

        serialPortService->setSerialPara(comboBox[0]->currentText(),getBaud(),getDataBits(),getStopBits(),getParity(),getFlowControl());

        emit sendStartSerialThread();

        for (int i = 0; i < FUNCTION_LENGTH; i++){
            comboBox[i]->setEnabled(false);
        }
        pushButton[1]->setText("关闭串口");
        pushButton[0]->setEnabled(true);
    }else{
        for (int i = 0; i < FUNCTION_LENGTH; i++){
            comboBox[i]->setEnabled(true);
        }

//        emit sendDestorySerialThread();
        emit sendCloseSerialThread();

        if(serialThread->isRunning()){
            qDebug()<<"********** serialThread()->isRunning()";
        }
        pushButton[1]->setText("打开串口");
        pushButton[0]->setEnabled(false);
    }
    qDebug() << "SerialTestPage::openSerialPortPushButtonClicked:" << QThread::currentThreadId();

}

6、UI如下:

image.png

image.png