QT线程 (8)

本文详细介绍了QT中的线程使用,包括为何需要线程、QT4和QT5线程的区别、如何创建和管理线程、处理复杂数据的线程使用、退出线程的方法、connect的队列与直接方式以及线程内操作UI的注意事项。

QT的线程



1、为什么要用线程?

看如下的dome:
ui的界面如下:
在这里插入图片描述
mywidget.h

#include <QWidget>
#include <QTimer>

QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget(QWidget *parent = nullptr);
    ~MyWidget();
    QTimer *ptime;
private slots:
    void on_pushButton_clicked();
    void TimeDealSlot(void);

private:
    Ui::MyWidget *ui;
};

mywidget.cpp

#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MyWidget)
{
    ui->setupUi(this);
    ptime = new QTimer(this);

    connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);

}

MyWidget::~MyWidget()
{
    delete ui;
}

void MyWidget::TimeDealSlot(void)
{
    static int i = 0;
    i++;
    ui->lcdNumber->display(i);
}

void MyWidget::on_pushButton_clicked()
{
    /* 启动定时器 */
    ptime->start(1000);
    /* 这个6s的延时当做是很复杂的数据处理 */
    QThread::sleep(6);

}

按下开始按钮启动定时器然后lcdNumber每隔一秒加1显示。但是后面有6s的延时的操作。其结果并不是我们想着这样。
真实的结果是6s执行完后lcdNumber每隔一秒才加1显示。因为在6s的延时时。都在处理这个延时。其它事情没法处理。
只能一件一件做完。当然如果每件事情都不是很复杂那也不会有影响。但这个复杂的6s延时是会有影响的。所以对于很复杂的
数据处理就需要另外开启一个线程。让这个线程来处理。

线程的优势:
在这里插入图片描述
多线程程序有以下几个特点:
在这里插入图片描述

2、QT线程

1. QT4线程

  1. 定义一个类继承与QThread。
  2. 在类里面重写void run()的虚函数(这个run就是线程处理函数并且run的访问限定符是protected。
  3. start()启动线程函数run。注意不能直接调用run()函数。
  4. 线程的退出Thread->quit() pThread->wait();
    模拟的流程如下:
    在这里插入图片描述
    mywidget.h
#include <QWidget>
#include <QTimer>
#include "mythread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget
{
    Q_OBJECT
public:
    MyWidget(QWidget *parent = nullptr);
    ~MyWidget();
    QTimer *ptime;
    Mythread *pThread;
private slots:
    void on_pushButton_clicked();
    void TimeDealSlot(void);
    void WidgetSlot(void);
private:
    Ui::MyWidget *ui;
};

mywidget.cpp

#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MyWidget)
{
    ui->setupUi(this);
    ptime = new QTimer(this);
    pThread = new Mythread(this);

    /* 定时器溢出信号timeout */
    connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);

    /* 当按窗口右上角x时,窗口触发destroyed()信号 */
    connect(this, &MyWidget::destroyed, this, &MyWidget::WidgetSlot);

}
MyWidget::~MyWidget()
{
    delete ui;
}

void MyWidget::TimeDealSlot(void)
{
    static int i = 0;
    i++;
    ui->lcdNumber->display(i);
}
void MyWidget::WidgetSlot(void)
{
    /* 停止线程 */
    pThread->quit();
    /* 等待线程处理完手头动作 */
    pThread->wait();
}
void MyWidget::on_pushButton_clicked()
{
    /* 如果定时器没有工作 */
    if(ptime->isActive() == false) {
        /* 启动定时器 */
        ptime->start(1000);
    }
    /* 启动线程 不能直接调run函数 */
    pThread->start();
}

mythread.h

#include <QThread>
class Mythread : public QThread
{
    Q_OBJECT
public:
    explicit Mythread(QObject *parent = nullptr);

protected:
    //QThread的虚函数
    //线程处理函数
    //不能直接调用,通过start()间接调用
    void run();
signals:

};

mythread.cpp

#include "mythread.h"

Mythread::Mythread(QObject *parent)
    : QThread{parent}
{

}

void Mythread::run()
{
    /* 这个6s的延时当做是很复杂的数据处理 */
    QThread::sleep(6);
}

当开启一个线程并且把这个延时放到线程里处理。按下开始按钮启动定时器然后lcdNumber每隔一秒加1显示。这样就不会有影响。

1. QT5线程在这里插入图片描述

ui的界面
在这里插入图片描述

  1. 创建一个类继承与QObject
  2. 在类中定义一个线程处理函数(有且只有一个)
#include <QObject>

class mythread : public QObject
{
    Q_OBJECT
public:
    explicit mythread(QObject *parent = nullptr);
    /* 自定义线程处理函数 */
    void ThreadDeal(void);
};
  1. 在主线程里定义自定义线程与 子线程。并且自定义线程不能指定父对象。指定了会报一个错误。
QThread *pThread; /* 子线程 */
mythread *pMyThread; /* 自定义线程 */
//动态分配空间,不能指定父对象
pMyThread = new mythread;
//创建子线程
pThread = new QThread(this);
  1. 把自定义线程加入到子线程。
    //把自定义线程加入到子线程中  子线程 与 自定义线程建立联系
    pMyThread->moveToThread(pThread);
  1. 启动子线程,但是没有启动线程处理函数
    //启动子线程,但是没有启动线程处理函数
   pThread->start();
  1. 在启动子线程后发送一个自定义信号。通过信号与槽去启动线程处理函数。直接调用启动是没有用的。
//只能通过 signal - slot 方式调用
emit startThread(); /* 发送信号 */
/* 接收信号去启动线程处理函数 */
connect(this, &MyWidget::startThread, pMyThread, &mythread::myTimeout);
  1. 线程退出
    pThread->quit();
    pThread->wait();

总体的流程:
在这里插入图片描述
mywidget.h

#include <QWidget>
#include <QThread>
#include "mythread.h"
#include <QTimer>

QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget(QWidget *parent = nullptr);
    ~MyWidget();
    QThread *pThread; /* 子线程 */
    mythread *pMyThread; /* 自定义线程 */
    QTimer *ptime;

private slots:
    void on_pushButtonStart_clicked();
    void on_pushButtonStop_clicked();
    void WidgetSlot(void);
    void TimeDealSlot(void);

signals:
    void StartThreadDealSignal();

private:
    Ui::MyWidget *ui;
};

mywidget.cpp

#include "mywidget.h"
#include "ui_mywidget.h"

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MyWidget)
{
    ui->setupUi(this);

    ptime = new QTimer(this);

    /* 自定义线程 不能指定父对象 */
    pMyThread = new mythread;

    /* 创建子线程 */
    pThread = new QThread(this);

    /* 把自定义线程加入到子线程 */
    pMyThread->moveToThread(pThread);

    /* 定时器溢出信号timeout */
    connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);

    /* 当按窗口右上角x时,窗口触发destroyed()信号 */
    connect(this, &MyWidget::destroyed, this, &MyWidget::WidgetSlot);

    /* 信号与槽去调用线程处理函数 */
    connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal);
}

MyWidget::~MyWidget()
{
    delete ui;
}

void MyWidget::TimeDealSlot(void)
{
    static int i = 0;
    i++;
    ui->lcdNumber->display(i);
}
void MyWidget::on_pushButtonStart_clicked()
{
    /* 线程启动已经开启就退出 */
    if(pThread->isRunning() == true) {
        return;
    }
    qDebug() << "主线程号:" << QThread::currentThread();
    /* 如果定时器没有工作 */
    if(ptime->isActive() == false) {
        /* 启动定时器 */
        ptime->start(1000);
    }

    /* 启动子线程 但并没有启动线程处理函数 */
    pThread->start();

    /* 发送StartThreadDealSignal信号启动线程处理函数 */
    emit StartThreadDealSignal();
}

void MyWidget::WidgetSlot(void)
{
    on_pushButtonStop_clicked();
    delete pMyThread;
}

void MyWidget::on_pushButtonStop_clicked()
{
    /* 线程启动已经停止就退出 */
    if(pThread->isRunning() == false) {
        return;
    }
    /* 停止线程 */
    pThread->quit();
    /* 等待线程处理完手头动作 */
    pThread->wait();
}

mythread.h

#include <QObject>

class mythread : public QObject
{
    Q_OBJECT
public:
    explicit mythread(QObject *parent = nullptr);
    /* 自定义线程处理函数 */
    void ThreadDeal(void);

signals:

};

mythread.cpp

#include "mythread.h"
#include<QThread>
#include<QDebug>
mythread::mythread(QObject *parent)
    : QObject{parent}
{

}

void mythread::ThreadDeal(void)
{
    /* 这个6s的延时当做是很复杂的数据处理 */
    QThread::sleep(6);
    qDebug() << "子线程号:" << QThread::currentThread();
}

3. QT5线程的退出

pThread->quit()是会等到线程手头的工作处理完才退出。但如果线程的工作处理不完。比如死循环。那怎么退出呢?

    /* 这个6s的延时当做是很复杂的数据处理 */
    while(1) {
        QThread::sleep(6);
        qDebug() << "子线程号:" << QThread::currentThread();
    }
  1. 强制退出
    用pThread->terminate()代替pThread->quit。但是这样可能会导致内存问题。强制退出风险很大。
  2. 在自定义类里定义一个标志位
    mythread.h
    void setThreadQuitFlag(int flag); /* 设置标志位函数 */
    int ThreadQuitFlag;  // 定义一个标志位

mythread.cpp

#include "mythread.h"
#include<QThread>
#include<QDebug>
mythread::mythread(QObject *parent)
    : QObject{parent}
{
    ThreadQuitFlag = false;
}

void mythread::ThreadDeal(void)
{
    /* 这个6s的延时当做是很复杂的数据处理 */
    while(1) {
        QThread::sleep(6);
        qDebug() << "子线程号:" << QThread::currentThread();
        if (ThreadQuitFlag == true) {
            break; // 退出线程
        }
    }

}
void mythread::setThreadQuitFlag(int flag)
{
    ThreadQuitFlag = flag;
}

在主线程里一开始启动线程设置标志为

pMyThread->setThreadQuitFlag(false);

在主线程里结束线程设置标志为true就可以退出线程

pMyThread->setThreadQuitFlag(true);

4. connect的第五个参数

只介绍队列与直接方式。多线程时才有意义。
如果是多线程,默认使用队列方式。
如果是单线程, 默认使用直接方式。
队列: 槽函数所在的线程和接收者一样。
直接:槽函数所在线程和发送者一样。

1. 直接方式

在如上QT5线程的dome中设置第五个参数 Qt::DirectConnection

    /* 信号与槽去调用线程处理函数 */
    connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal, Qt::DirectConnection);

在这里插入图片描述
槽函数所在线程和发送者(主窗口)一样。

2. 队列方式

在如上QT5线程的dome中设置第五个参数 Qt::DirectConnection

    /* 信号与槽去调用线程处理函数 */
    connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal, Qt::QueuedConnection);

在这里插入图片描述
队列: 槽函数所在的线程和接收者(子线程)一样。

3、QT线程绘制QImage图片然后给到主线程显示刷新。

具体的流程:
1.点击绘图按钮调用线程处理函数
2. 线程里绘图图片但是不显示。绘制一次刷新一次。发送图片给主线程
3. 主线程拿到子线程的图片。在主窗口刷新显示。
伪代码如下:
在这里插入图片描述
注意:QImage才能在线程里绘图。
ui界面如下:就一个按钮
在这里插入图片描述

widget.h

#include <QWidget>
#include "mythread.h"
#include <QThread>
#include <QImage>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    QTimer *pTime;
    //重写绘图事件
    void paintEvent(QPaintEvent *);
public slots:
    void getImage(QImage); //槽函数
    void dealClose(); //窗口关闭槽函数

private:
    Ui::Widget *ui;
    QImage image;
    MyThread *myT; //自定义线程对象
    QThread *thread; //子线程
};

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
#include <QThread>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->resize(600,600);

    //自定义类对象,分配空间,不可以指定父对象
    myT = new MyThread;

    //创建子线程
    thread = new QThread(this);

    //把自定义模块添加到子线程
    myT->moveToThread(thread);

    //启动子线程,但是,并没有启动线程处理函数
    thread->start();

    //线程处理函数,必须通过signal - slot 调用
    connect(ui->pushButton, &QPushButton::pressed, myT, &MyThread::drawImage);
    connect(myT, &MyThread::updateImage, this, &Widget::getImage);

    connect(this, &Widget::destroyed, this, &Widget::dealClose);

}

Widget::~Widget()
{
    delete ui;
}

void Widget::dealClose()
{
    //退出子线程
    thread->quit();
    //回收资源
    thread->wait();
    delete myT;

}

void Widget::getImage(QImage temp)
{
    image = temp;
    update(); //更新窗口,间接调用paintEvent()
}

void Widget::paintEvent(QPaintEvent *)
{
    QPainter p(this); //创建画家,指定绘图设备为窗口
    p.drawImage(50, 50, image);
}

mythread.h

#include <QObject>
#include <QImage>

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);

    //线程处理函数
    void drawImage();

signals:
    void updateImage(QImage temp);

public slots:
};

mythread.cpp

#include "mythread.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QImage>
#include <QRandomGenerator>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread::drawImage()
{
    //定义QImage绘图设备
    QImage image(500, 500, QImage::Format_ARGB32); // QImage::Format_ARGB32透明的颜色
    //定义画家,指定绘图设备
    QPainter p(&image);
    //定义画笔对象
    QPen pen;
    pen.setWidth(5); //设置宽度
    //把画笔交给画家
    p.setPen(pen);

    //定义画刷
    QBrush brush;
    brush.setStyle(Qt::SolidPattern); //设置样式
    brush.setColor(QColor(QRandomGenerator::global()->bounded(0,255),QRandomGenerator::global()->bounded(0,255),
                          QRandomGenerator::global()->bounded(0,255))); //随机设置颜色
    //把画刷交给画家
    p.setBrush(brush);
    //定义点
    QPoint a[] =
    {
       QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
       QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
       QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
       QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
       QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
       QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500))
    };

    /* 绘制多边形 */
    p.drawPolygon(a, sizeof(a)/sizeof(a[0]));
    //通过信号发送图片
    emit updateImage(image);
}

4、多线程使用过程中注意事项

需要移动到子线程中处理的模块类,创建的对象的时候不能指定父对象。但在关闭窗口时要记得释放。

    //自定义类对象,分配空间,不可以指定父对象
    myT = new MyThread;

线程不能操作UI对象(从Qwidget直接或间接派生的窗口对象)

#include "mythread.h"
#include<QThread>
#include<QDebug>
#include<QMessageBox>
mythread::mythread(QObject *parent)
    : QObject{parent}
{
    ThreadQuitFlag = false;
}
void mythread::ThreadDeal(void)
{
    /* 这个6s的延时当做是很复杂的数据处理 */
    while(1) {
        QThread::sleep(6);
        qDebug() << "子线程号:" << QThread::currentThread();
        if (ThreadQuitFlag == true) {
            break; // 退出线程
        }

        /* 在线程处理函数里操作UI对象 */
        QMessageBox::about(NULL,"关于","在线程处理函数内部不能操作图形界面");
    }

}

QMessageBox::about(NULL,“关于”,“在线程处理函数内部不能操作图形界面”)
这样会导致程序崩掉。
在这里插入图片描述
在线程处理函数内部不能操作图形界面。只能是纯数据处理。

5.总结

主要介绍QT的线程的两种用法。connect的第五个参数 。以及线程退pThread->terminate()与pThread->quit函数不一样。以及用pThread->quit。如果线程退不出来(死循环)用标志位来退出。还有线程里不能操作图形界面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值