Qt多线程编程—使用Qt自身的线程类

Qt多线程编程—使用Qt自身的线程类

使用Qt自身的线程类进行多线程编程,有两种方法。
**方法1概述:**首先用一个类继承QThread类,然后重新改写QThread类的虚函数run()。只要实例化该类,然后调用函数start(),就可以开启新的多线程(run()会被自动调用),所以线程的具体实现过程放在run()里面。
**方法二概述:**先用一个类继承QObject类,然后在该类中添加一个成员函数working()(名字无所谓)用来处理具体子线程的工作,在主线程调用QObject类提供的moveToThread()方法。

方法1具体步骤

1:需要创建一个线程类的子类,让其继承QT中的线程类 QThread,比如:

class MyThread:public QThread
{
    ......
}

2:重写父类的 run() 方法,在该函数内写子线程要处理的具体操作

class MyThread:public QThread
{
    ......
 protected:
    void run()
    {
        ........
    }
}

3:在主线程中创建子线程对象实例,关键字new

MyThread * subThread = new MyThread;

4:启动子线程, 调用 start() 方法

subThread->start();

方法1实操

这里以一个计数器的小案例为例,创建一个对话框项目,实现主线程和子线程之间的通信,启动线程计数时,右下加的计数器显示开始读秒,点击重置计数器按钮时,读秒数清零后继续计数,点击停止线程按钮时,计数停止。
在这里插入图片描述

(1)新建一个MyThread类,继承QThread
在这里插入图片描述
(2)在MyThread.cpp中重写MyThread类的虚函数run(),这就是子线程实现的具体功能函数
在这里插入图片描述
(3)对ui界面的四个按钮设置对应的槽函数on_pushButton_clicked() - on_pushButton_clicked3(),在需要用到多线程的地方实例化MyThread,当点击按钮“启动线程计数”(按钮1)时,调用MyThread::start()函数,会开启对应的线程,自动运行重写的run()函数。

//dialog.h
#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include "MyThread.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class Dialog;
}
QT_END_NAMESPACE

class Dialog : public QDialog
{
    Q_OBJECT

public:
    Dialog(QWidget *parent = nullptr);
    ~Dialog();

private slots:
    void on_pushButton_clicked();
    void on_pushButton_2_clicked();
    void on_pushButton_3_clicked();

    void DisplayMsg(int num);
private:
    Ui::Dialog *ui;
    MyThread *t;
signals:
    void ResetSignal();

};
#endif // DIALOG_H
//dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
#include <MyThread.h>

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::Dialog)
{
    ui->setupUi(this);
    t = new MyThread();//实例化
    connect(t, SIGNAL(TestSignal(int)), this, SLOT(DisplayMsg(int)));
    connect(this, SIGNAL(ResetSignal()), t, SLOT(ResetSlot()));
}

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

void Dialog::on_pushButton_clicked() // 线程开始按钮
{
    t->start();
}
void Dialog::on_pushButton_2_clicked() //重置计数按钮
{
    emit ResetSignal();
}

void Dialog::on_pushButton_3_clicked() //停止按钮
{
    t->terminate();
}

void Dialog::DisplayMsg(int num)
{
    ui->label->setText(QString::number(num));
}

在这里插入图片描述

在这个案例中,通过改变number的值后,返回到主线程中再在ui界面上进行显示,为什么不直接在子线程中进行对ui的操作呢?这是因为Qt中的子线程无权操作程序中的窗口类型对象, 这是不允许的,所以都要通过主线程来操作。

方法2具体步骤

1:创建一个新的类,让这个类从QObject派生

class MyWork:public QObject
{
    .......
}

2:在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的具体操作

class MyWork:public QObject
{
public:
    .......
    // 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加
    void working();
}

3:在主线程中创建一个QThread对象, 这就是子线程的对象

QThread* sub = new QThread;

4:在主线程中创建工作的类对象**(千万不要指定给创建的对象指定父对象)**

MyWork* work = new MyWork(this);    // error
MyWork* work = new MyWork;          // ok

5:将MyWork对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法

// void QObject::moveToThread(QThread *targetThread);
// 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub);	// 移动到子线程中工作

方法2实操

同样也以一个简单的计数器来说明,点击开始,数值显示区域开始计数。
在这里插入图片描述
(1)在MyThread.h中新建一个MyThread类,继承QObject类,这一点就与第一种方法不同了,继承的类不同。这个类中添加一个公共的成员函数working(),函数体就是我们要子线程中执行的具体操作,在MyThread.cpp中实现具体代码。
在这里插入图片描述
在这里插入图片描述
(2)在主线程中创建一个QThread对象sub, 这就是子线程的对象,再创建一个工作类对象work,将工作类对象移动到创建的子线程当中去work->moveToThread(sub); 随后调用start()启动函数。此外,在Ui界面中设置按钮pushButton和标签label,运行项目就可以得到一个在子线程中运行的计数器。

//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include "MyThread.h"

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

    //创建线程类对象
    QThread* sub = new QThread(this);
    //创建工作类对象  注意不要指定父对象
    MyThread* work = new MyThread;
    //将工作类对象移动到创建的子线程当中去
    work->moveToThread(sub);
    // 启动线程
    sub->start();
    //点击开始按钮,开始工作
    connect(ui->pushButton,&QPushButton::clicked,work,&MyThread::working);
    //显示数据
    connect(work,&MyThread::curNumber,this,[=](int num)
            {
        ui->label->setNum(num);
    });

    //释放资源
    connect(this,&MainWindow::destroyed,this,[=](){
        sub->quit();
        sub->wait();
        sub->deleteLater(); //等价于 delete sub;

        work->deleteLater();

    });
}

MainWindow::~MainWindow()
{
    delete ui;
}
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>


QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

};
#endif // MAINWINDOW_H

在这里插入图片描述

注意:

程序结束后对资源的释放时很关键的一步,不然程序容易崩溃,本案例中添加了如下代码,用以释放创建的线程类对象sub和工作类对象work,当窗口关闭的时候执行。
在这里插入图片描述

两种方法对比

相比而言,第一种方式在实现上是比较简单的,但是也有弊端,假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。第二种方法实现就比较灵活,对于多个任务(比如显示多个界面)各分配一个线程去执行。这样就避免了自定义好多个类继承自QThread类,从而可以避免冗余。所以当处理的任务多时,优先考虑第二种方法,当任务量比较少时,可以采用第一种方法。

注:

本博客参考了一个叫大丙的博主的教程大丙课堂-QT线程篇,他有好多优质的文章,大家可以学习参考。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值