QT5创建线程的两种方法(一)----继承Qthread

文章目录

  • QT5创建线程
    • 前言
    • UI设计
      • 新建Qt Widgets Application项目
      • 编辑UI界面
    • 线程类编写
      • 源码
      • 代码重点详解
        • 继承QThread新建线程类
        • pro文件
        • Q_OBJECT宏定义
        • 对串口的设置的方法
        • 打开串口
        • 接收串口数据
        • 主线程和线程间的通讯
          • 根据串口打开状态,设置复选框的状态
          • 根据串口接收的数据,在text browser上显示
    • 常见的问题
      • 跨线程对象创建对象

QT5创建线程

前言

QT5创建线程有两种方法,一种是qt4.6之前的方法,即创建一个自己的线程类继承QThread类。另一种是qt5后官方推荐的方法,即创建一个Object继承Qobject类,将自己要在线程里实现的方法和对象,在该类中定义。然后在主线程里实例化一个QThread对象,利用Qobject类的moveToThread方法,将自己创建的Object类都移到该线程里,这样Object类里的槽函数都是在新的线程里运行的了。
首先讲第一种方法,这边通过新建一个串口接收的例子来详细讲解。
示例程序是采用Qt Creator 4.11.1编辑和编译的。

UI设计

QT窗口应用程序中的主线程都是UI线程,也就是main函数这个函数入口的执行线程,所有的UI对象和方法也只能在主线程里调用。

新建Qt Widgets Application项目

QT5创建线程的两种方法(一)----继承Qthread_第1张图片
Qt Widgets Application的模板已经为我们写好了初始的ui界面和mainwidows类。我们在这个基础上编辑UI界面和编写程序。

编辑UI界面

QT5创建线程的两种方法(一)----继承Qthread_第2张图片
串口通讯需要设置:端口号波特率数据位奇偶校验位停止位流控制等参数,利用QTUI设计界面的Combo BOX组件(复选框)来选择串口的设置,利用push button组件来控制线程的开启和关闭,利用Text Browser组件来显示接收的串口数据。
为了编程方便需要在Ui设计界面对所有的组件进行重新命名。
QT5创建线程的两种方法(一)----继承Qthread_第3张图片

线程类编写

源码

不贴源码了,传送门。

代码重点详解

继承QThread新建线程类

在h文件中,我们定义一个线程类继承QThread,准备重载QThread类的虚函数run()

#include 
#include 
class SerialsThread:public QThread
{
    Q_OBJECT
public:
    SerialsThread();
    ~SerialsThread();
protected:
    void run() override;
private:
    QSerialPort *myQSerialPort=nullptr;
signals:
    void portStatus(bool);
    void datareceived();
private slots:
    void receivedata();

};

在cpp文件中,我们重载run()run()函数里的任务就是工作在新的线程里的。可打印现在的线程号,与UI线程号对比,我们会发现它们工作在不同的线程。run()函数,当在主线程里调用start()函数,便会调用,在没有事件循环或者while循环的情况下,当代码执行完,线程便会结束。不建议用terminate()函数强行结束线程。

void SerialsThread::run()
{
    qDebug()<<"SerialsThread is running"<<QThread::currentThread();
    //do the job
}

pro文件

在写串口线程类前,我们要在pro文件里用上serialport模块,在pro文件里加上一行代码。这样我们就可以使用< QSerialPort >这个串口类。

QT       += serialport

Q_OBJECT宏定义

我们知道,QT最有特色的机制就是,信号和槽的机制,这个宏定义就设置了相关的东西。有了这个宏定义,我们才能使用信号和槽。

对串口的设置的方法

    myQSerialPort->setParity(QSerialPort::NoParity);//设置奇偶校验位为0
    myQSerialPort->setDataBits(QSerialPort::Data8);//设置数据位为8bit
    myQSerialPort->setFlowControl(QSerialPort::NoFlowControl);//设置流控制为OFF
    myQSerialPort->setStopBits(QSerialPort::OneStop);//设置停止位为1
    myQSerialPort->setBaudRate(QSerialPort::Baud9600);//设置波特率9600
     myQSerialPort->setPortName(myuserview.PortName);//设置端口号myuserview.PortName,根据复选框的选择

打开串口

if(!myQSerialPort->open(QIODevice::ReadWrite))//用ReadWrite 的模式尝试打开串口
    {
        qDebug()<<myuserview.PortName<<"打开失败";
        return;
    }

接收串口数据

首先在重写的run函数中,链接readyRead信号和接收数据的槽函数,readyRead信号是当有新的的数据可以被读取时,就会发射这个信号,QT的帮助文档中是这样描述的。
tips:F1 进入光标上函数或对象的帮助文档。

This signal is emitted once every time new data is available for reading from the device’s current read channel

connect(myQSerialPort,&QSerialPort::readyRead,this,&SerialsThread::receivedata);

链接好信号和槽函数后,需要开启事件循环。这个很重要,否则会什么东西都接收不到。

exec();//开启事件循环

在槽函数中,调用readAll方法。并发射 void datareceived() 信号,这个信号链接主线程中的槽函数,可用来跟新text browser的数据。

void SerialsThread::receivedata()
{
    QByteArray array=myQSerialPort->readAll();
    if(!array.isEmpty())
    {
        serialsreceivebuffer=serialsreceivebuffer.append(array);
        emit datareceived();
    }
}

主线程和线程间的通讯

根据串口打开状态,设置复选框的状态

如果想实现,串口接收线程中,如果打开串口成功,则在主线程中的复选框变为不可选的状态这个功能。就要利用信号和槽的机制,在串口线程中发射信号,并且在主线程的槽函数中响应,并设置复选框的状态。

主线程中,我们链接串口线程对象中的 void portStatus(bool) 信号和Mainwindow对象中的槽函数。

 connect(myserialsthread,&SerialsThread::portStatus,this,&MainWindow::serialsportStatusUpdate);

并根据信号传递的bool量参数,设置复选框的状态。

void MainWindow::serialsportStatusUpdate(bool status)
{
    ui->COMCB->setEnabled(!status);
    ui->BaudCB->setEnabled(!status);
}

串口线程,我们打开串口的代码的基础上,加上发射信号void portStatus(bool)

if(!myQSerialPort->open(QIODevice::ReadWrite))//用ReadWrite 的模式尝试打开串口
    {
        qDebug()<<myuserview.PortName<<"打开失败";
        emit portStatus(false);
        return;
    }
    else
    {
        emit portStatus(true);
    }
根据串口接收的数据,在text browser上显示

首先链接myserialsthreaddatareceived信号和Mainwindow的更新text browser的槽函数。

connect(myserialsthread,&SerialsThread::datareceived,this,&MainWindow::seriasreceiveTBupdate);

更新text browser的槽函数显示serialsreceivebuffer接收buffer,当buffer超过一定行数时,要清空text browser,当数据很多的时候,如果不清空的话,程序会异常。

void MainWindow::seriasreceiveTBupdate()
{
    //ui->ReceiveTB->insertPlainText(QString(serialsreceivedata));
    ui->ReceiveTB->setText(QString(serialsreceivebuffer));
    if(ui->ReceiveTB->document()->lineCount()>20)//当textbrowser行数超过20行后,清空textbrowser
    {
        ui->ReceiveTB->clear();
        serialsreceivebuffer.clear();
    }
}

常见的问题

跨线程对象创建对象

利用继承QThread类方法来新建线程很容易出现跨线程对象创建对象的问题。例如,如果想实现在开启事件循环前,对打开的串口发送个''connect success''的字符串,我们这样写。

SerialsThread::SerialsThread()
{
    myQSerialPort=new QSerialPort();
}
void SerialsThread::run()
{
//这边做设置串口,和打开串口的操作,省略不写
	connect(myQSerialPort,&QSerialPort::readyRead,this,&SerialsThread::receivedata);
    myQSerialPort->write("connected");
    exec();
}

这时就会报如下警告,意思是,不能在子线程中为主线程的父对象创建子对象。

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QSerialPort(0x2dd4c40), parent’s thread is QThread(0x1c9650), current thread is SerialsThread(0x2dd4c00)

出现这个问题的原因是,在构造函数中实例化了myQSerialPort对象,因为SerialsThread这个类我们是在主线程的MainWindow构造函数里实例化的。QThread的特性是,除了run函数里运行的代码是属于子线程的,其他的都属于创建它的线程,也就是主线程。所以myQSerialPort是属于主线程的,我们调用write时,会为myQSerialPort这个对象创建子对象,这时就会报出这个警告。
如果不调用write,调用上文讲到的串口设置和打开方法,或者readAll方法,是不会出现这个问题的,因为这些方法没有创建子对象。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //user
    myserialsthread = new SerialsThread();
}

其实要更改也很简单,一种是不要在子线程里调用write,第二种是将实例化的操作放进run里。

SerialsThread::SerialsThread()
{
    //myQSerialPort=new QSerialPort();
}
void SerialsThread::run()
{
	myQSerialPort=new QSerialPort();
//这边做设置串口,和打开串口的操作,省略不写
	connect(myQSerialPort,&QSerialPort::readyRead,this,&SerialsThread::receivedata);
    myQSerialPort->write("connected");
    exec();
}

这里的exec()事件循环很重要,如果不使用的话,会出现,myQSerialPort->write("connected")返回7,表示已经写入7个字节,但接收的串口却没有任何东西收到。根据官方文档,这是因为,QIODevice的通讯是异步的,调用write时,只是将字节成功写入了缓存区,但没有实际写入设备,只有到事件循环返回时,才会写入设备。这个问题,这个博客讲的很好,传送们。

Certain subclasses of QIODevice, such as QTcpSocket and QProcess, are asynchronous. This means that I/O functions such as write() or read() always return immediately, while communication with the device itself may happen when control goes back to the event loop.

你可能感兴趣的:(qt5,多线程,c++,串口通信)