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位编译器。代码可以直接复制粘贴运行

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 【Linux学习】Linux命令卸载软件

    【Linux学习】Linux命令卸载软件1、打开一个终端,输入dpkg–list,按下Enter键,终端输出以下内容,显示的是你电脑上安装的所有软件。2、在终端中找到你需要卸载的软件的名称,列表是按照首字母排序的。3、在终端上输入命令sudoapt-get–purgeremove包名(–purge是可选项,写上这个属性是将软件及其配置文件一并删除,如不需要删除配置文件,可执行sudoapt-getremove包名),此处我要删除的是polipo,那么在终端输入sudoapt-get–purgeremovep

  • 前端的模块化_模块化设计

    前端的模块化_模块化设计前言在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀,此时在JS方面就会考虑使用模块化规范去管理。本文内容主要有理解模块化,为什么要模块化,模块化的优缺点以及模块化规范,并且介绍下开发中…

    2022年10月23日
  • Ubuntu安装gcc-7.3.0[通俗易懂]

    Ubuntu安装gcc-7.3.0[通俗易懂]下载gcc-7.3.0.tar.gzfromhttps://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-7.3.0/gcc-7.3.0.tar.gz.有一些依赖提前安装:sudoaptinstalllibgmp-devsudoaptinstalllibmpfr-devsudoaptinstalllibmpc-devsudoaptinstalllibisl-devsudoaptinstallzlib1g-dev(建.

  • 弗洛伊德算法怎么理解_弗洛伊德算法思想

    弗洛伊德算法怎么理解_弗洛伊德算法思想这个方法中,其中每一个顶点到另一个顶点最多就是两步。所以就是找到两个顶点的最近距离packagea;importjava.lang.reflect.Array;importjava.util.Arrays;publicclassFloydDemo{publicstaticvoidmain(String[]args){char[]di…

    2022年10月23日
  • 所谓的代码段、数据段

    所谓的代码段、数据段

  • 第一天来到新公司的volg (ETL开发工程师)[通俗易懂]

    第一天来到新公司的volg (ETL开发工程师)[通俗易懂]第一天来到新公司的volg(ETL开发工程师)新的改变首先自我介绍一下,我是一名刚刚大学毕业的程序猿,在大学完了两年,到最后大三一年才开始认真的去学习编程的各种知识,开源框架,看视频代码。现在是大数据时代,我也想跟着潮流,所以我第一份工作就选择了ETL开发,为以后大数据开发做基础铺垫,毕竟现在大数据开发都没公司直接招实习生或刚刚毕业的人。新的工作先说一下今天来公司吧,上午大概就是9点到…

发表回复

您的电子邮箱地址不会被公开。

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