Qt多线程实现方式-moveToThread及其注意事项
- Chapter1 Qt多线程实现方式-moveToThread
- 一、Qt下使用线程主要有两种方法。
- 二、Qt下创建多线程也有两种方法。
- 三、其它问题。
- Chapter2 QT多线程接收串口数据
- 1.前言
- 2.功能作用
- 3.软件测试效果
- 4.基本步骤
- Chapter3 利用Qt多线程机制实现双路串口数据流的接收和发送
- 1. 主程序界面
- 2. 两个子线程的线程号(调试信息中输出)
- Chapter4 QT多线程——解决因串口数据过多而导致程序界面崩溃的问题
- 一 前言
- 二 步骤
- 2.2 代码实现
- 三 总结
- Chapter5 Qt子线程、多线程使用串口 QSerialPort QThread QObject
特别注意:
通过obj->movetothread(thread)并不是将Object中所有的函数都移动到子线程当中。只有通过槽函数连接的才在子线程中,可以通过qDebug()打印其currentThreadId多试试。
Chapter1 Qt多线程实现方式-moveToThread
原文链接:https://blog.csdn.net/k331922164/article/details/70990239
一、Qt下使用线程主要有两种方法。
一种是传统的继承QThread类,重写run方法。
class WorkerThread : public QThread{Q_OBJECTvoid run() override {QString result;/* ... here is the expensive or blocking operation ... */emit resultReady(result);}signals:void resultReady(const QString &s);};void MyObject::startWorkInAThread(){WorkerThread *workerThread = new WorkerThread(this);connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);workerThread->start();}
该方法已经落伍了,主要原因线程不安全,需要自己手动加锁,比较麻烦,所以推荐使用方法二。
定义一个工作线程(Worker类)继承QObject,在主线程(Controller类)中创建QThread对象、Worker对象,Worker对象调用moveToThread方法。
class Worker : public QObject{Q_OBJECTpublic slots:void doWork(const QString ¶meter) {QString result;/* ... here is the expensive or blocking operation ... */emit resultReady(result);}signals:void resultReady(const QString &result);};class Controller : public QObject{Q_OBJECTQThread workerThread;public:Controller() {Worker *worker = new Worker;worker->moveToThread(&workerThread);connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);connect(this, &Controller::operate, worker, &Worker::doWork);connect(worker, &Worker::resultReady, this, &Controller::handleResults);workerThread.start();}~Controller() {workerThread.quit();workerThread.wait();}public slots:void handleResults(const QString &);signals:void operate(const QString &);};
这样一来,整个Worker对象都移入线程中(线程安全),然后在主线程中每发射一次信号给工作线程,工作线程的槽函数就执行一次。
工作线程执行完,再发射信号到主线程中,以便释放内存。
新建一个Woker对象和一个QThread对象,才能创建一个线程,如果要创建若干多个线程,则需要若干个Woker对象和QThread对象了。
二、Qt下创建多线程也有两种方法。
一种是使用容器(如:QVector类、QList类)去装入多个Worker对象和多个QThread对象,使用[](类似数组的操作),即可访问单个对象。
另一种是使用并发类QtConcurrent。
三、其它问题。
1、使用线程时,能编译通过但是提示段错误,原因是没有在构造函数内new Worker对象和QThread对象。
2、内存泄漏,线程做完时,需要调用quit方法、wait方法,还要delete Worker对象和QThread对象。如果后面还要使用该线程,则再加上new Worker对象和QThread对象。
3、调用任务管理器,可以观察到是否出现内存泄漏。没有任何操作,内存使用量不停增加,即为内存泄漏。
Chapter2 QT多线程接收串口数据
原文链接:https://blog.csdn.net/aptblaze/article/details/118003195
1.前言
QT多线程的使用,和绝大数人一样,犯了错误(请查阅Qt开发人员( Bradley T. Hughes)Blog中的文章 you are-doing-it-wrong介绍)。为了解决问题,网上查阅学习了几十篇文章,基本都是错误的使用方法,或者不完整,未能给予正确的引导。
为方便后来学习者,少走弯路,于是自己动手写了一下程序,过程不再赘述,只以完整的案例进行教学,内部注释较多,可供大家阅读、思考。
2.功能作用
使用多线程,避免上位机软件与单片机等硬件设备高速通讯时,造成软件界面假死、丢包等现象。同时对串口进行了简单的封装,方便调用。本文提供了完整的源代码,方便测试。有较详细的注释方便阅读、思考。编译环境为QT5.8.0,Qt Creator4.2.1
3.软件测试效果
4.基本步骤
(1)pro文件添加QT5自带的头文件
QT += serialport
(2)serialworker.h头文件
#include "serialworker.h"SerialWorker::SerialWorker(QSerialPort *ser, QObject *parent) : QObject(parent),serial(ser)
{}
QString SerialWorker::ByteArrayToHexString(QByteArray data)
{QString ret(data.toHex().toUpper());int len = ret.length()/2;qDebug()<<"收到字节长度为:"<<len;for(int i=1;i<len;i++){ret.insert(2*i+i-1," ");}return ret;
}void SerialWorker::doDataReciveWork()
{ QByteArray buffer = serial->readAll();// 2.进行数据处理QString result = ByteArrayToHexString(buffer);qDebug() << "子线程收到数据:" << result << "线程ID:" << QThread::currentThreadId();// 3.将结果发送到主线程emit sendResultToGui(result);
}
(3)mainwindow.h文件
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle("子线程读串口");this->setMinimumSize(600,248);this->setMaximumSize(1200,496);InitSerialPortName();//1.新建串口处理子线程SerialWorker *ser = new SerialWorker(&serial_1);ser->moveToThread(&serialThread_1);// 2.连接信号和槽QString s;connect(&serialThread_1, &QThread::finished, ser, &QObject::deleteLater); // 线程结束,自动删除对象connect(&serial_1, &QSerialPort::readyRead, ser, &SerialWorker::doDataReciveWork); // 主线程通知子线程接收数据的信号connect(ser, &SerialWorker::sendResultToGui, this, &MainWindow::handleResults); // 主线程收到数据结果的信号// connect(ser,SIGNAL(sendResultToGui(QString)), this, SLOT(handleResults(QString))); //主线程收到数据结果的信号写法2// 3.开始运行子线程serialThread_1.start(); // 线程开始运行
}MainWindow::~MainWindow()
{serialThread_1.quit();serialThread_1.wait();delete ui;
}void MainWindow::InitSerialPortName()
{// 清空下拉框ui->box_portName->clear();//通过QSerialPortInfo查找可用串口foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){QString showName = info.portName();qDebug() << showName+info.description();ui->box_portName->addItem(showName);}//波特率QStringList baudrateList;baudrateList<<"4800"<<"9600"<<"19200"<<"38400"<<"57600"<<"115200";ui->box_baudrate->addItems(baudrateList);//添加下拉列表选项ui->box_baudrate->setCurrentText("115200");//界面中初始值// ui->box_baudrate->setView(new QListView(this));//该设置是配合qss的,不然item行高设置没效果//数据位QStringList databitList;databitList<<"5"<<"6"<<"7"<<"8";ui->box_dataBits->addItems(databitList);ui->box_dataBits->setCurrentText("8");
// ui->box_dataBits->setView(new QListView(this));//校验位QStringList parityList;parityList<<"无"<<"奇"<<"偶";ui->box_parityBit->addItems(parityList);ui->box_parityBit->setCurrentText("No");
// ui->box_parityBit->setView(new QListView(this));//停止位QStringList stopbitList;stopbitList<<"1"<<"2";ui->box_stopBit->addItems(stopbitList);ui->box_stopBit->setCurrentText("1");
// ui->box_stopBit->setView(new QListView(this));//流控制
// QStringList flowctrlList;
// flowctrlList<<"No"<<"Hardware"<<"Software";
// ui->boxFlowControl->addItems(flowctrlList);
// ui->boxFlowControl->setCurrentText("No");ui->boxFlowControl->setView(new QListView(this));
}void MainWindow::on_btn_openPort_clicked()
{if(ui->btn_openPort->text()==QString("打开串口")){//设置串口名QString portName = (ui->box_portName->currentText()).split(":").at(0);qDebug() <<"当前打开串口为:"<<portName;serial_1.setPortName(portName);//设置波特率serial_1.setBaudRate(ui->box_baudrate->currentText().toInt());//设置停止位if(ui->box_stopBit->currentText() == "1")serial_1.setStopBits(QSerialPort::OneStop);else if(ui->box_stopBit->currentText() == "2")serial_1.setStopBits(QSerialPort::TwoStop);//设置数据位数if(ui->box_dataBits->currentText() == "8")serial_1.setDataBits(QSerialPort::Data8);else if(ui->box_dataBits->currentText() == "7")serial_1.setDataBits(QSerialPort::Data7);else if(ui->box_dataBits->currentText() == "6")serial_1.setDataBits(QSerialPort::Data6);else if(ui->box_dataBits->currentText() == "5")serial_1.setDataBits(QSerialPort::Data5);//设置奇偶校验if(ui->box_parityBit->currentText() == "无")serial_1.setParity(QSerialPort::NoParity);else if(ui->box_parityBit->currentText() == "偶")serial_1.setParity(QSerialPort::EvenParity);else if(ui->box_parityBit->currentText() == "奇")serial_1.setParity(QSerialPort::OddParity);// //设置流控制
// serial_1.setFlowControl(QSerialPort::NoFlowControl);//打开串口if(!serial_1.open(QIODevice::ReadWrite)){QMessageBox::about(NULL, "提示", "无法打开串口!");return;}//下拉菜单控件失能ui->box_portName->setEnabled(false);ui->box_baudrate->setEnabled(false);ui->box_dataBits->setEnabled(false);ui->box_parityBit->setEnabled(false);ui->box_stopBit->setEnabled(false);ui->btn_openPort->setText(QString("关闭串口"));}else{//关闭串口serial_1.close();//下拉菜单控件使能ui->box_portName->setEnabled(true);ui->box_baudrate->setEnabled(true);ui->box_dataBits->setEnabled(true);ui->box_parityBit->setEnabled(true);ui->box_stopBit->setEnabled(true);ui->btn_openPort->setText(QString("打开串口"));}
}void MainWindow::on_btn_clearText_clicked()
{ui->browser_dataReceive->clear();
}void MainWindow::handleResults(const QString &result)
{qDebug() << "主线程收到结果数据:" << result << "线程ID:" << QThread::currentThreadId();//从界面中读取以前收到的数据QString oldString = ui->browser_dataReceive->toPlainText()+'\n';oldString = oldString + QString(result);//清空以前的显示ui->browser_dataReceive->clear();//重新显示ui->browser_dataReceive->append(oldString);
}
(4)mainwindow.cpp文件
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle("子线程读串口");this->setMinimumSize(600,248);this->setMaximumSize(1200,496);InitSerialPortName();//1.新建串口处理子线程SerialWorker *ser = new SerialWorker(&serial_1);ser->moveToThread(&serialThread_1);// 2.连接信号和槽QString s;connect(&serialThread_1, &QThread::finished, ser, &QObject::deleteLater); // 线程结束,自动删除对象connect(&serial_1, &QSerialPort::readyRead, ser, &SerialWorker::doDataReciveWork); // 主线程通知子线程接收数据的信号connect(ser, &SerialWorker::sendResultToGui, this, &MainWindow::handleResults); // 主线程收到数据结果的信号// connect(ser,SIGNAL(sendResultToGui(QString)), this, SLOT(handleResults(QString))); //主线程收到数据结果的信号写法2// 3.开始运行子线程serialThread_1.start(); // 线程开始运行
}MainWindow::~MainWindow()
{serialThread_1.quit();serialThread_1.wait();delete ui;
}void MainWindow::InitSerialPortName()
{// 清空下拉框ui->box_portName->clear();//通过QSerialPortInfo查找可用串口foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){QString showName = info.portName();qDebug() << showName+info.description();ui->box_portName->addItem(showName);}//波特率QStringList baudrateList;baudrateList<<"4800"<<"9600"<<"19200"<<"38400"<<"57600"<<"115200";ui->box_baudrate->addItems(baudrateList);//添加下拉列表选项ui->box_baudrate->setCurrentText("115200");//界面中初始值// ui->box_baudrate->setView(new QListView(this));//该设置是配合qss的,不然item行高设置没效果//数据位QStringList databitList;databitList<<"5"<<"6"<<"7"<<"8";ui->box_dataBits->addItems(databitList);ui->box_dataBits->setCurrentText("8");
// ui->box_dataBits->setView(new QListView(this));//校验位QStringList parityList;parityList<<"无"<<"奇"<<"偶";ui->box_parityBit->addItems(parityList);ui->box_parityBit->setCurrentText("No");
// ui->box_parityBit->setView(new QListView(this));//停止位QStringList stopbitList;stopbitList<<"1"<<"2";ui->box_stopBit->addItems(stopbitList);ui->box_stopBit->setCurrentText("1");
// ui->box_stopBit->setView(new QListView(this));//流控制
// QStringList flowctrlList;
// flowctrlList<<"No"<<"Hardware"<<"Software";
// ui->boxFlowControl->addItems(flowctrlList);
// ui->boxFlowControl->setCurrentText("No");ui->boxFlowControl->setView(new QListView(this));
}void MainWindow::on_btn_openPort_clicked()
{if(ui->btn_openPort->text()==QString("打开串口")){//设置串口名QString portName = (ui->box_portName->currentText()).split(":").at(0);qDebug() <<"当前打开串口为:"<<portName;serial_1.setPortName(portName);//设置波特率serial_1.setBaudRate(ui->box_baudrate->currentText().toInt());//设置停止位if(ui->box_stopBit->currentText() == "1")serial_1.setStopBits(QSerialPort::OneStop);else if(ui->box_stopBit->currentText() == "2")serial_1.setStopBits(QSerialPort::TwoStop);//设置数据位数if(ui->box_dataBits->currentText() == "8")serial_1.setDataBits(QSerialPort::Data8);else if(ui->box_dataBits->currentText() == "7")serial_1.setDataBits(QSerialPort::Data7);else if(ui->box_dataBits->currentText() == "6")serial_1.setDataBits(QSerialPort::Data6);else if(ui->box_dataBits->currentText() == "5")serial_1.setDataBits(QSerialPort::Data5);//设置奇偶校验if(ui->box_parityBit->currentText() == "无")serial_1.setParity(QSerialPort::NoParity);else if(ui->box_parityBit->currentText() == "偶")serial_1.setParity(QSerialPort::EvenParity);else if(ui->box_parityBit->currentText() == "奇")serial_1.setParity(QSerialPort::OddParity);// //设置流控制
// serial_1.setFlowControl(QSerialPort::NoFlowControl);//打开串口if(!serial_1.open(QIODevice::ReadWrite)){QMessageBox::about(NULL, "提示", "无法打开串口!");return;}//下拉菜单控件失能ui->box_portName->setEnabled(false);ui->box_baudrate->setEnabled(false);ui->box_dataBits->setEnabled(false);ui->box_parityBit->setEnabled(false);ui->box_stopBit->setEnabled(false);ui->btn_openPort->setText(QString("关闭串口"));}else{//关闭串口serial_1.close();//下拉菜单控件使能ui->box_portName->setEnabled(true);ui->box_baudrate->setEnabled(true);ui->box_dataBits->setEnabled(true);ui->box_parityBit->setEnabled(true);ui->box_stopBit->setEnabled(true);ui->btn_openPort->setText(QString("打开串口"));}
}void MainWindow::on_btn_clearText_clicked()
{ui->browser_dataReceive->clear();
}void MainWindow::handleResults(const QString &result)
{qDebug() << "主线程收到结果数据:" << result << "线程ID:" << QThread::currentThreadId();//从界面中读取以前收到的数据QString oldString = ui->browser_dataReceive->toPlainText()+'\n';oldString = oldString + QString(result);//清空以前的显示ui->browser_dataReceive->clear();//重新显示ui->browser_dataReceive->append(oldString);
}
(5)serialworker.cpp文件
#include "serialworker.h"SerialWorker::SerialWorker(QSerialPort *ser, QObject *parent) : QObject(parent),serial(ser)
{}
QString SerialWorker::ByteArrayToHexString(QByteArray data)
{QString ret(data.toHex().toUpper());int len = ret.length()/2;qDebug()<<"收到字节长度为:"<<len;for(int i=1;i<len;i++){ret.insert(2*i+i-1," ");}return ret;
}void SerialWorker::doDataReciveWork()
{ QByteArray buffer = serial->readAll();// 2.进行数据处理QString result = ByteArrayToHexString(buffer);qDebug() << "子线程收到数据:" << result << "线程ID:" << QThread::currentThreadId();// 3.将结果发送到主线程emit sendResultToGui(result);
}
Chapter3 利用Qt多线程机制实现双路串口数据流的接收和发送
原文链接:https://blog.csdn.net/SmartTiger_CSL/article/details/104383717
在上一篇文章的基础上,编写了一个对话框程序,可同时收发两路串口数据,每一路串口均在独立的子线程中实现。增加了清空edit的按钮。
1. 主程序界面
2. 两个子线程的线程号(调试信息中输出)
主线程的ID号为0x179c,两个串口子线程类的构造均是在主线程中,串口的启动、接收数据均在各自的子线程中,子线程ID号分别在0x14e4和0x5b0。而串口的关闭是在主线程中。这是和connect的配置有关。代码如下:
#include "serialcontroller.h"
#include <QDebug>SerialController::SerialController(QObject *parent) : QObject(parent)
{m_portId = -1;
}SerialController::~SerialController()
{if(m_serialThread.isRunning()){m_serialPort->closePort();delete m_serialPort;m_serialThread.quit();m_serialThread.wait();}}
void SerialController::initCtrl(int portId,QString portName,long portBaud)
{m_portId = portId;m_portName = portName;m_portBaud = portBaud;qDebug()<<"Controller is running in main thread: "<<QThread::currentThreadId();//实例对象保存在堆上,没有父对象的指针要想正常销毁,需要将线程的 finished() 信号关联到 QObject 的 deleteLater() 让其在正确的时机被销毁m_serialPort = new SerialPort(m_portId,m_portName,m_portBaud);//m_serialPort对象不能有父对象。m_serialPort->moveToThread(&m_serialThread);connect(this,&SerialController::startRunning,m_serialPort,&SerialPort::startPort);connect(&m_serialThread,&QThread::finished,m_serialPort,&QObject::deleteLater);connect(this,&SerialController::ctrlSendData,m_serialPort,&SerialPort::write_Data);//从主线程发来的数据写入串口connect(m_serialPort,&SerialPort::receive_data,this,&SerialController::ctrlReceiveData);//从串口读取的数据发送给主线程
}
void SerialController::startCtrl()
{m_serialThread.start();emit startRunning();}
void SerialController::stopCtrl()
{if(m_serialThread.isRunning()){ m_serialPort->closePort();m_serialThread.quit();//会自动发送finished信号m_serialThread.wait(); }
}
串口的启动和接收数据所在的函数startPort与QThread类的connect信号关联,因此是在子线程中执行;而串口的关闭函数closePort没有与connect关联,不是槽函数,是在SerialController类的stopCtrl中执行,SerialController存在于主线中,因此closePort在主线程中执行。
因此,这里验证里上一篇文章中的重点(3):
(3)controller、worker 对象到底在哪个线程?「在哪创建就属于哪」这句话放在任何地方都是适用的。而 moveToThread() 函数的作用是将槽函数在指定的线程中被调用。也就是说controller、worker对象均在主线程中。除了绑定在connect上的槽函数(及槽函数体调用的函数)外,worker的其余函数也在主线程中执行。这个connect发送者可以是SerialController本身(this),也可以是m_serialThread
moveToThread()并不是将整个worker 对象“搬移”到controller线程中,而是将connect中的槽函数放到controller线程中执行。不注意这一点的话,很可能出现“QObject::Cannot create children for a parent that is in a different thread”问题。或者出现“耗时工作代码”仍在主线程中运行的情况。
Chapter4 QT多线程——解决因串口数据过多而导致程序界面崩溃的问题
原文链接:https://blog.csdn.net/DIVIDADA/article/details/131209469
一 前言
最近做了一个QT上位机。上位机通过串口接收下位机的数据,下位机对数据进行计算处理并将处理后的结果显示在程序界面上。程序中,下位机每隔200ms给QT上位机发送一帧20字节的数据,但是会经常出现界面卡顿并崩溃的情况,当然,我可以通过修改下位机数据发送频率(如1S发送一帧数据)的方法来解决这个问题,但是这种解决方法不太优雅且有一定的缺陷,于是我使用了QT多线程——除了GUI线程外,另开一个子线程,专门用来处理串口数据。
二 步骤
2.1 方法描述
QThread 类提供了一个与平台无关的管理线程的方法。一个 QThread 对象管理一个线程。QThread 的执行从 run() 函数的执行开始,在 Qt 自带的 QThread 类中,run() 函数通过调用 exec() 函数来启动事件循环机制,并且在线程内部处理 Qt 的事件。在 Qt 中建立线程的主要目的就是为了用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作。
定义一个继承于 QObject 的 worker 类,在 worker 类中定义一个槽函数 doWork(),这个函数中接收和处理串口数据,并将处理好的数据发送给GUI线程。
在GUI线程的 controller 类中,新建一个 QThread 的对象和 woker 类对象,使用 moveToThread() 方法将 worker 对象的事件循环全部交由 QThread 对象处理。
建立相关的信号函数和槽函数进行连接,GUI线程发出信号触发 QThread 的槽函数处理串口数据,数据处理完毕后QThread 子线程发送信号触发GUI线程槽函数接收处理好的串口数据。
2.2 代码实现
2.2.1 子线程中
新建一个继承与QObject的子线程类:SerialWorker
上面为修改serialworker.h
上面为修改serialworker.cpp
2.2.2 GUI线程中
上面为修改widget.h
上面为修改widget.c
三 总结
经测试,使用子线程来收发串口数据,可以提升程序运行的稳定性,解决因为串口数据量过大而造成界面卡顿的问题。
Chapter5 Qt子线程、多线程使用串口 QSerialPort QThread QObject
https://blog.csdn.net/u014779536/article/details/111721947