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、部分代码如下(完整代码已嵌入目前项目,已测试稳定,不崩溃,因为花了挺久时间,不无偿公开。
思路可以无偿公开:如上面第四条描述)
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如下: