Qt学习笔记(三十六):多线程


一、线程的创建方法一:

1、自定义一个类(MyThread) ,继承于 QThread;

2、重写 QThread 的 run() 方法(run() 方法就是线程处理函数,将耗时的操作放在该方法中执行);

3、调用线程对象的 start() 方法启动线程,执行线程处理函数;注意:不能直接调用 run() 方法;

效果图:

子线程类:

mythread.h:

#include <QThread>

// 将父类改成 QThread
class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    
signals:
    void threadOver(); // 自定义一个线程结束信号
    
public slots:
    
protected:
    // 重写父类的 run() 方法;
    // run() 方法就是线程处理函数,耗时的操作都放在这个方法中执行;
    // 不能直接调用,要通过 start() 方法触发;
    // 如果直接调用,就不是线程处理函数了,而是一个普通函数;
    void run(); 
};

mythread.cpp:

#include "mythread.h"
#include <QDebug>

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

// 线程处理函数
void MyThread::run()
{
    // 处理耗时的操作
    for (int i = 0; i < 10; i++)
    {
        qDebug() << i;
        
        QThread::sleep(1);
    }
    
    // 线程结束之后发送自定义的 threadOver 信号
    emit threadOver();
}

在主界面中调用:

widget.h:

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

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT
    
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    
private slots:
    void on_btnStart_clicked();
    
    void on_btnStop_clicked();
    
private:
    Ui::Widget *ui;
    
    QTimer *timer;      // 定时器对象
    MyThread *thread;   // 线程对象
};

widget.cpp:

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

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    timer = new QTimer(this);
    thread = new MyThread(this);

    // 当定时器超时时触发(即间隔指定时间到了就会触发)
    connect(timer, &QTimer::timeout, [=](){
		// 在 LCD Number 控件上递加显示数据
        static int i = 0;
        ui->lcdNumber->display(i++);
    });
    
    // 线程结束时发出 threadOver 信号
    connect(thread, &MyThread::threadOver, [=](){
        qDebug() << "thread is over";
        
        // 停止定时器
        timer->stop();
    });
}

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

// 开始
void Widget::on_btnStart_clicked()
{
    if (timer->isActive() == false)
    {
        // 启动定时器,间隔指定时间触发一次 timeout 信号
        timer->start(500);
        
        // 模拟耗时操作:如果在 ui 线程中执行,那么在循环期间,主界面无响应!!
//        for (int i = 0; i < 10; i++)
//        {
//            qDebug() << i;
            
//            QThread::sleep(1);
//        }
        
        // 将耗时操作放在线程中;
        // start() 方法用于启动线程,执行线程处理函数 run() 方法;
        thread->start();
    }
}

// 停止
void Widget::on_btnStop_clicked()
{
    if (timer->isActive())
    {
        // 停止定时器
        timer->stop();
    }
    
    // 停止线程
    // 不建议使用 terminate() 方法停止线程;因为 terminate() 方法被调用的时候,会立即停止线程,
    // 此时如果线程正在操作内存空间,就会导致内存泄漏;因为内存还没有被释放,线程就停止了。
//    thread->terminate();
    
    // 建议使用 quit() 方法停止线程,该方法会等待线程执行完毕再停止;
    thread->quit();
}

二、线程的创建方法二:

1、自定义一个类(MyThread),直接或间接继承于 QObject(不需要继承于 QThread);

2、在类中定义一个线程处理函数,函数名随便起,不是必须为 run();但是线程处理函数只能有一个;

3、创建自定义类的对象:MyThread *myThread = new MyThread;注意:不能指定父对象;

4、创建一个子线程对象:QThread *thread = new QThread(this);

5、把自定义线程类,加入到子线程对象中:myThread->moveToThread(thread);

6、启动子线程对象:thread->start(); 注意:此时只是把子线程开启了,并没有启动我们自定义线程类中的线程处理函数;

7、启动自定义的线程处理函数,只能通过 信号和槽 的方法启动;把线程处理函数当做一个槽函数;

效果图:

mythread.h:

#include <QObject>

// 自定义类继承于 QObject
class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    
    // 声明一个线程处理函数(线程处理函数只能有一个)
    void MyTimer();
    
    // 私有的属性要通过一个公有的方法来操作
    void setFlag(bool flag);
    
signals:
    void mySignal();    // 定时器发送的信号
    
public slots:
    
private:
    bool isStop; // 设置一个标志位,标志是否停止线程
    
};

mythread.cpp:

#include "mythread.h"
#include <QDebug>
#include <QThread>

MyThread::MyThread(QObject *parent) : QObject(parent)
{
}

// 线程处理函数:模拟定时器,每隔一秒发送一次信号
void MyThread::MyTimer()
{
    // 死循环,每隔 1 秒发送一次信号
    while (!isStop)
    {
        // 发送信号
        emit mySignal();
        
        QThread::sleep(1);
        
        qDebug() << "子线程号:" << QThread::currentThread();
        
        // 退出循环,停止线程
        if (isStop)
        {
            break;
        }
    }
}

// 公有的方法操作私有的属性
void MyThread::setFlag(bool flag)
{
    this->isStop = flag;
}

widget.h:

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

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT
    
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    
private slots:
    void on_btnStart_clicked();
    
    void on_btnStop_clicked();
    
signals:
    void startThread(); // 启动子线程的信号
    
private:
    Ui::Widget *ui;
    
    MyThread *myThread; // 自定义线程对象
    QThread *thread;    // 子线程对象
};

widget.cpp:

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

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 创建自定义线程对象;不能指定父对象;
    // 因为指定父对象之后,调用 moveToThread() 加入到子线程会报错;
    myThread = new MyThread();
    
    // 创建子线程对象;可以指定父对象
    thread = new QThread(this);
    
    // 把自定义线程加入到子线程中
    myThread->moveToThread(thread);
    
    qDebug() << "主线程号:" << QThread::currentThread();
    
    // 处理自定义线程中 每隔一秒发送的信号
    connect(myThread, &MyThread::mySignal, [=](){
        static int i = 0;
        ui->lcdNumber->display(i++);
    });
    
    // 当发出 startThread 信号的时候,执行自定义线程中的 线程处理函数(MyTimer)
    connect(this, &Widget::startThread, myThread, &MyThread::MyTimer);
    
    // 退出程序的时候也要停止线程
    connect(this, &Widget::destroyed, [=](){
        on_btnStop_clicked();
    });
    
    // 注意:线程处理函数内部,不允许操作图形界面。
}

Widget::~Widget()
{
    delete ui;
    
    // 因为 myThread 创建的时候没有指定父对象,
    // 所以在程序退出的时候,需要手动释放。
    delete myThread;
}

// 开始
void Widget::on_btnStart_clicked()
{
    if (!thread->isRunning())
    {
        // 启动线程:
        // 此时只是把子线程开启了,并没有启动我们自定义线程类中的线程处理函数;
        thread->start();
        
        // 启动线程设置标志位
        myThread->setFlag(false);
        
        // 不能直接调用线程处理函数;如果直接调用,线程处理函数就变成了一个普通函数,
        // 一个在主线程中执行的普通函数,就会导致主线程卡死;
//        myThread->MyTimer();
        
        // 只能通过信号和槽的方式调用线程处理函数
        emit startThread();
    }
}

// 停止
void Widget::on_btnStop_clicked()
{
    if (thread->isRunning())
    {
        // 停止线程设置标志位
        myThread->setFlag(true);
        
        // 停止线程:会等待线程执行结束后,再退出线程;
        // 如果线程是个死循环,那么线程就会一直运行,不会退出;
        // 所以:退出线程最好的方法是在线程中设置一个标志位;
        thread->quit();
        thread->wait();
    }
}

三、线程画图案例:使用线程创建的第二种方法;

1、自定义一个类(MyThread),直接或间接继承于 QObject(不需要继承于 QThread);

2、在类中定义一个线程处理函数,函数名随便起,不是必须为 run();但是线程处理函数只能有一个;

3、创建自定义类的对象:MyThread *myThread = new MyThread;注意:不能指定父对象;

4、创建一个子线程对象:QThread *thread = new QThread(this);

5、把自定义线程类,加入到子线程对象中:myThread->moveToThread(thread);

6、启动子线程对象:thread->start(); 注意:此时只是把子线程开启了,并没有启动我们自定义线程类中的线程处理函数;

7、启动自定义的线程处理函数,只能通过 信号和槽 的方法启动;把线程处理函数当做一个槽函数;

效果图:点击按钮时,随机画图五角形;

mythread.h:

#include <QObject>
#include <QImage>

// 自定义类继承于 QObject
class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    
    // 声明一个线程处理函数(线程处理函数只能有一个)
    void drawImage();
    
signals:
    void updateImage(QImage);    // 声明一个信号
};

mythread.cpp:

#include "mythread.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QPoint>

MyThread::MyThread(QObject *parent) : QObject(parent)
{
}

// 线程处理函数:画图
void MyThread::drawImage()
{
    // 声明一个 QImage 绘图设备
    QImage image(300, 300, QImage::Format_ARGB32);
    
    // 声明一个画家,指定绘图设备
    QPainter painter(&image);
    
    // 声明一个画笔
    QPen pen;
    pen.setWidth(5); // 设置画笔宽度
    
    // 把画笔交给画家
    painter.setPen(pen);
    
    // 声明一个画刷
    QBrush brush;
    brush.setStyle(Qt::SolidPattern);   // 设置画刷样式
    brush.setColor(Qt::red);            // 设置画刷颜色
    
    // 把画刷交给画家
    painter.setBrush(brush);
    
    // 随机定义 5 个点
    QPoint p[] = {
        // qrand() 取随机数;对 300 取余目的是保证随机数在 300 以内;
        QPoint(qrand() % 300, qrand() % 300), 
        QPoint(qrand() % 300, qrand() % 300),
        QPoint(qrand() % 300, qrand() % 300),
        QPoint(qrand() % 300, qrand() % 300),
        QPoint(qrand() % 300, qrand() % 300)  
    };
    
    // 画一个多边形。参数1表示点的数组指针;参数2表示点的个数;
    painter.drawPolygon(p, 5);
    
    // 通过信号将图片发送出去
    emit updateImage(image);
}

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();
    
    // 重写绘图事件
    void paintEvent(QPaintEvent *);
    
    // 声明一个槽函数,获取线程绘制的图片
    void getImage(QImage);
    
private:
    Ui::Widget *ui;
    
    QImage image;      // 图片对象
    
    MyThread *myThread; // 自定义线程对象
    QThread *thread;    // 子线程对象
};

widget.cpp:

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

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    this->resize(350, 350);
    
    // 创建自定义线程对象;不能指定父对象;
    // 因为指定父对象之后,调用 moveToThread() 加入到子线程会报错;
    myThread = new MyThread();
    
    // 创建子线程对象;可以指定父对象
    thread = new QThread(this);
    
    // 把自定义线程加入到子线程中
    myThread->moveToThread(thread);
    
    // 启动子线程(但是没有启动线程处理函数)
    thread->start();
    
    // 点击按钮时启用线程处理函数,开始画图
    connect(ui->pushButton, &QPushButton::clicked, myThread, &MyThread::drawImage);
    
    // 当子线程发送 updateImage 信号将绘制好的图片传出时,主线程使用 getImage 槽函数接收
    connect(myThread, &MyThread::updateImage, this, &Widget::getImage);
    
    // 程序退出的时候,退出子线程,回收资源
    connect(this, &Widget::destroyed, [=](){
        thread->quit(); // 退出线程
        thread->wait(); // 等待回收资源
    });
}

Widget::~Widget()
{
    delete ui;
    
    // 因为 myThread 创建的时候没有指定父对象,
    // 所以在程序退出的时候,需要手动释放。
    delete myThread;
}

// 绘图事件
void Widget::paintEvent(QPaintEvent *)
{
    // 声明一个画家
    QPainter painter(this);
    
    // 画一张图片
    painter.drawImage(20, 20, image);
}

// 接收子线程发送过来的图片
void Widget::getImage(QImage img)
{
    this->image = img;
    
    // 更新窗口,触发 paintEvent 绘图事件
    update();
}

 

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值