QThread的用法

概述QThread类提供了一个与平台无关的管理线程的方法。一个QThread对象管理一个线程。QThread的执行从run()函数的执行开始,在Qt自带的QThread类中,run()函数通过调用exec()函数来启动事件循环机制,并且在线程内部处理Qt的事件。在Qt中建立线程的主要目的就是为了用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作。下面就谈谈如何利用QT

大家好,又见面了,我是你们的朋友全栈君。

概述

       QThread类提供了一个与平台无关的管理线程的方法。一个QThread对象管理一个线程。QThread的执行从run()函数的执行开始,在Qt自带的QThread类中,run()函数通过调用exec()函数来启动事件循环机制,并且在线程内部处理Qt的事件。在Qt中建立线程的主要目的就是为了用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作。QThread的使用方法有如下两种:

  1. QObject::moveToThread()

  2. 继承QThread类

下面通过具体的方法描述和例子来介绍两种方法。

方法一. QObject::moveToThread()方法

方法描述

  1. 定义一个继承于QObject的worker类,在worker类中定义一个槽slot函数doWork(),这个函数中定义线程需要做的工作。
  2. 在要使用线程的controller类中,新建一个QThread的对象和woker类对象,使用moveToThread()方法将worker对象的事件循环全部交由QThread对象处理。
  3. 建立相关的信号函数和槽函数进行连接,然后发出信号触发QThread的槽函数,使其执行工作。

moveToThread的例子

       首先新建一个work类,该类重点在于其doWork槽函数,这个函数定义了线程需要做的工作,需要向其发送信号来触发。Wrok类的头文件中定义了全部函数,其cpp文件为空,因此就不贴出来了。

Wroker.h的定义如下

// work定义了线程要执行的工作
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include<QDebug>
#include<QThread>

class Worker:public QObject
{ 
   
    Q_OBJECT
public:
    Worker(QObject* parent = nullptr){ 
   }
public slots:
     // doWork定义了线程要执行的操作
    void doWork(int parameter)
    { 
   
        qDebug()<<"receive the execute signal---------------------------------";
        qDebug()<<" current thread ID:"<<QThread::currentThreadId();
       // 循环一百万次
       for(int i = 0;i!=1000000;++i)
       { 
   
        ++parameter;
       }
       // 发送结束信号
       qDebug()<<" finish the work and sent the resultReady signal\n";
       emit resultReady(parameter);
    }

// 线程完成工作时发送的信号
signals:
    void resultReady(const int result);
};

#endif // WORKER_H

       然后定义一个Controller类,这个类中定义了一个QThread对象,用于处理worker对象的事件循环工作。

controller.h的定义如下:

#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include<QThread>
#include<QDebug>

// controller用于启动线程和处理线程执行结果
class Controller : public QObject
{ 
   
    Q_OBJECT
    QThread workerThread;
public:
    Controller(QObject *parent= nullptr);
    ~Controller();

public slots:
	// 处理线程执行的结果
    void handleResults(const int rslt)
    { 
   
        qDebug()<<"receive the resultReady signal---------------------------------";
        qDebug()<<" current thread ID:"<<QThread::currentThreadId()<<'\n';
        qDebug()<<" the last result is:"<<rslt;
    }
signals:
	// 发送信号触发线程
    void operate(const int);

};

#endif // CONTROLLER_H

       Controller类的cpp文件,其构造函数中创建worker对象,并且将其事件循环全部交给workerThread对象来处理,最后启动该线程,然后触发其事件处理函数。

controller.cpp的定义如下:

#include "controller.h"
#include <worker.h>
Controller::Controller(QObject *parent) : QObject(parent)
{ 
   
    Worker *worker = new Worker;
    //调用moveToThread将该任务交给workThread
    worker->moveToThread(&workerThread);
    //operate信号发射后启动线程工作
    connect(this, SIGNAL(operate(const int)), worker, SLOT(doWork(int)));
    //该线程结束时销毁
    connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
    //线程结束后发送信号,对结果进行处理
    connect(worker, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
    //启动线程
    workerThread.start();
    //发射信号,开始执行
    qDebug()<<"emit the signal to execute!---------------------------------";
    qDebug()<<" current thread ID:"<<QThread::currentThreadId()<<'\n';
    emit operate(0);
}
//析构函数中调用quit()函数结束线程
Controller::~Controller()
{ 
   
    workerThread.quit();
    workerThread.wait();
}

       接下来就是主函数,主函数中我们新建一个Controller对象,开始执行:

main.cpp的内容如下

#include <QCoreApplication>
#include "controller.h"
#include<QDebug>
#include<QThread>
int main(int argc, char *argv[])
{ 
   
    qDebug()<<"I am main Thread, my ID:"<<QThread::currentThreadId()<<"\n";
    QCoreApplication a(argc, argv);

    Controller c;
    return a.exec();
}

运行结果及说明

这里写图片描述


运行结果截图 1

       main函数中打印当前线程编号,即主线程的线程编号是0X7a4, 在Controller的构造函数中继续打印当前线程编号,也是主线程编号,之后把work类的工作交给子线程后,给子线程发送信号,子线程收到了信号开始执行,其线程号为0X1218,执行结束后发送信号给Controller处理结果。

方法二. 继承QThread的方法

方法描述

  1. 自定义一个继承QThread的类MyThread,重载MyThread中的run()函数,在run()函数中写入需要执行的工作.
  2. 调用start()函数来启动线程。

继承QThread的例子

       首先写MyThread类,该类继承于QThread,该类中自定义了信号槽和重写了run函数。头文件如下:

mythread.h内容如下

#ifndef MYTHREAD_H
#define MYTHREAD_H
#include<QThread>
#include<QDebug>
class MyThread : public QThread
{ 
   
    Q_OBJECT
public:
    MyThread(QObject* parent = nullptr);
    //自定义发送的信号
signals:
    void myThreadSignal(const int);
    //自定义槽
public slots:
    void myThreadSlot(const int);
protected:
    void run() override;
};

#endif // MYTHREAD_H

mythread.cpp内容如下

#include "mythread.h"

MyThread::MyThread(QObject *parent)
{ 
   

}

void MyThread::run()
{ 
   
    qDebug()<<"myThread run() start to execute";
    qDebug()<<" current thread ID:"<<QThread::currentThreadId()<<'\n';
    //循环一百万次
    int count = 0;
    for(int i = 0;i!=1000000;++i)
    { 
   
     ++count;
    }
    // 发送结束信号
    emit myThreadSignal(count);
    exec();
}

void MyThread::myThreadSlot(const int val)
{ 
   
    qDebug()<<"myThreadSlot() start to execute";
    qDebug()<<" current thread ID:"<<QThread::currentThreadId()<<'\n';
    // 循环一百万次
    int count = 888;
    for(int i = 0;i!=1000000;++i)
    { 
   
     ++count;
    }
}

       在Controller类中实现这MyThread的调用。

controller.h内容如下(评论区的老哥们指出图片贴错了,现在已改正。)

#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include <QThread>
#include <QDebug>

// controller用于启动线程和处理线程执行结果
class Controller : public QObject
{ 
   
    Q_OBJECT
    QThread* myThrd;
public:
    Controller(QObject *parent= nullptr);
    ~Controller();

public slots:
    // 处理线程执行的结果
    void handleResults(const int rslt)
    { 
   
        qDebug()<<"receive the resultReady signal---------------------------------";
        qDebug()<<" current thread ID:"<<QThread::currentThreadId()<<'\n';
        qDebug()<<" the last result is:"<<rslt;
    }
signals:
    // 发送信号触发线程
    void operate(const int);

};

#endif // CONTROLLER_H

controller.cpp内容如下

#include "controller.h"
#include "mythread.h"
Controller::Controller(QObject *parent) : QObject(parent)
{ 
   
    myThrd = new MyThread;
    connect(myThrd,SIGNAL(&MyThread::myThreadSignal),this, SLOT(&Controller::handleResults));
    // 该线程结束时销毁
    connect(myThrd, SIGNAL(&QThread::finished), this, SLOT(&QObject::deleteLater));
    connect(this,SIGNAL(&Controller::operate),myThrd, SLOT(&MyThread::myThreadSlot));

    // 启动该线程
    myThrd->start();
    QThread::sleep(5);
    emit operate(999);
}

Controller::~Controller()
{ 
   
    myThrd->quit();
    myThrd->wait();
}

main函数的内容和上例中相同,因此就不贴了。

运行结果和说明:这里写图片描述

运行结果截图2

       通过自定义一个继承QThread的类,实例化该类的对象,重载run()函数为需要做的工作。然后在需要的地方调用start函数来执行run函数中的任务。然而有趣的是,myThread.start()之后我又从主函数触发了一个信号,对应于子线程的槽,子线程的槽函数中打印当前执行的线程的编号,可以看到,执行子线程的槽函数的线程编号却是主线程的编号

两种方法的比较

       两种方法来执行线程都可以,随便你的喜欢。不过看起来第二种更加简单,容易让人理解。不过我们的兴趣在于这两种使用方法到底有什么区别?其最大的区别在于:

  1. moveToThread方法,是把我们需要的工作全部封装在一个类中,将每个任务定义为一个的槽函数,再建立触发这些槽的信号,然后把信号和槽连接起来,最后将这个类调用moveToThread方法交给一个QThread对象,再调用QThread的start()函数使其全权处理事件循环。于是,任何时候我们需要让线程执行某个任务,只需要发出对应的信号就可以。其优点是我们可以在一个worker类中定义很多个需要做的工作,然后发出触发的信号线程就可以执行。相比于子类化的QThread只能执行run()函数中的任务,moveToThread的方法中一个线程可以做很多不同的工作(只要发出任务的对应的信号即可)。
  2. 子类化QThread的方法,就是重写了QThread中的run()函数,在run()函数中定义了需要的工作。这样的结果是,我们自定义的子线程调用start()函数后,便开始执行run()函数。如果在自定义的线程类中定义相关槽函数,那么这些槽函数不会由子类化的QThread自身事件循环所执行,而是由该子线程的拥有者所在线程(一般都是主线程)来执行。如果你不明白的话,请看,第二个例子中,子类化的线程的槽函数中输出当前线程的ID,而这个ID居然是主线程的ID!!事实的确是如此,子类化的QThread只能执行run()函数中的任务直到run()函数退出,而它的槽函数根本不会被自己的线程执行。

PS:
       以上代码是Qt5.7开发环境,采用的是VS2015的64位编译器。代码可以直接复制粘贴运行

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/124587.html原文链接:https://javaforall.net

(0)
上一篇 2022年4月3日 下午2:00
下一篇 2022年4月3日 下午2:00


相关推荐

  • linux(1)Mac上传文件到Linux服务器

    linux(1)Mac上传文件到Linux服务器前言我们使用mac时,想让本地文件上传至服务器,该怎么办呢windows系统,我们可以使用xftp或者rz命令,那么mac呢?mac系统,我们可以使用sftp、scp或者rz命令,本文介绍sft

    2022年7月29日
    7
  • 安装HAXM「建议收藏」

    安装HAXM「建议收藏」老师给的是在网上下载HAXM。但事实上打开这里你会发现Android已经自动下载了HAXM因此你要做的是找到HAXM路径,然后继续安装它。我的路径是C:\Users\DELL\AppData\Local\Android\Sdk\extras\intel\Hardware_Accelerated_Execution_Manager…

    2022年6月28日
    108
  • ssh免密码登录配置方法,(图示加命令)

    ssh免密码登录配置方法,(图示加命令)首先 说明一下我们要做的是 serverA 服务器的 usera 用户免密码登录 serverB 服务器的 userb 用户 我们先使用 usera 登录 serverA 服务器 root serverA su usera usera serverA pwd home usera 然后在 serverA 上生成密钥对 usera serverA s

    2026年3月17日
    1
  • ubuntu16安装中文输入法_ubuntu输入法安装

    ubuntu16安装中文输入法_ubuntu输入法安装本文安装谷歌输入法。使用一段时间后发现,谷歌输入法用起来极舒服,比sougouforlinux好用多了。记得谷歌的中文输入法主要是北京分部在做,对googlecn的好感度飙升!!!安装fcitx-googlepinyin(Ctrl+Alt+T打开终端,输入)sudoapt-getinstallfcitx-googlepinyin输入密码开始安装(输入密码的时候光标是不会移动的,不会有对应密码的***这样的星号出来,只管输完密码按回车就行),命令行会停在[y/n]的确认行,输入y并

    2026年4月13日
    5
  • argmin函数解析

    argmin函数解析argmin 函数一般的用法为 argminf x 通俗意义上的解释是 argmin 表示使目标函数 f x 取最小值时的变量值 argmax 函数其用法类似 argmaxf x 同理可知 argmax 表示使目标函数 f x 取最大值时的变量值详情请参照 http www cppblog com guijie archive 2010 12 13 136273 html

    2026年3月18日
    2
  • spring cloud搭建教程

    spring cloud搭建教程Springcloud 是一个基于 SpringBoot 实现的服务治理工具包 在微服务架构中用于管理和协调服务的微服务 就是把一个单体项目 拆分为多个微服务 每个微服务可以独立技术选型 独立开发 独立部署 独立运维 并且多个服务相互协调 相互配合 最终完成用户的价值 SpringCloud 是一系列框架的有序集合 其主要的设施有 服务发现与注册 配置中心 消息总线 负载均衡 断路器 数据监控等 通过 SpringBoot 的方式 可以实现一键启动 和部署

    2026年3月17日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号