您现在的位置是:首页 >技术杂谈 >QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。网站首页技术杂谈

QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。

「QT(C++)开发工程师」 2023-05-14 16:00:02
简介QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。


🙉 🙉本人Qt专栏->动态更新🙉 🙉


👷 👷在QT中你需要明白,main函数或者自定义的C++类或者Qt设计师界面等,都属于主线程,如果在主线程进行一些大批量数据计算,可能会导致界面卡屏,点击有延时或者根本无法点击。这种情况是很严重的。例如:进行大文件读写、进行无限嵌套,进行大量数据计算,多个循环嵌套等都会导致界面假死现象,这篇文章希望可以帮助你解决这些烦恼,直接进入主题。👷 👷

第一种 主线程(GUI)

🙉 🙉我们创建的第一个Qt项目就是主线程,也称为GUI线程,这个大家了解即可。

第二种 子线程1继承自QThread

本类槽函数和run都是线程事件,可以进行耗时操作
👮 👮特点:👮 👮

  1. 自己创建的类需要继承 QThread 类
  2. 需要重写父类run方法(线程事件入口):void run() override;
  3. 在主线程创建线程对象,至于在那些主线程类来创建由你自己定。
  4. 自己继承线程的这个类一定不能指定父对象。
  5. 凡是自己创建的线程类,一定不能在本类里面操作UI界面组件,一般通过信号与槽与主线程进行交互。
  6. 线程完成任务,记得释放内存,QT官方规定。
  7. 创建第1种线程对象 此类继承自QThread,并将其移动到线程(在构造函数执行),特点【run函数是线程事件】【槽函数是线程事件】
movetothread4 thread_5;

🙉 🙉 下面以代码来讲解🙉 🙉

头文件 movetothread4.h

讲解:这是我自己创建的类,继承QThread,头文件需要添加:

  1. #include “QObject” ------》》》继承祖类,可以使用信号与槽机制
  2. #include “QThread” ------》》》线程头必加
  3. #include"QDebug" ------》》》打印
  4. void run() override; 可以写在私有、保存、共有无妨。
  5. 线程对象在当前类创建,这是区别其他继承的不同点。
#ifndef MOVETOTHREAD4_H
#define MOVETOTHREAD4_H

#include <QObject>
#include <QThread>
#include<QDebug>
#include "file.h"
class movetothread4 : public QThread
{
    Q_OBJECT
public:
    movetothread4();
    ~movetothread4();
    QThread * thread;
signals:
    void sig_sendfile(QString log);
public slots:
    void slot_sendfile(QString log);
private slots:
private:
    file fileobj;
    void run() override;
};

#endif // MOVETOTHREAD4_H

源文件 movetothread4.cpp

重点
【1】创建线程对象,可以是指针对象,也可以是栈对象,推荐使用栈吧,指针都要自己手动删除。

thread = new QThread;

【2】将本类对象移动到线程,相当于一个任务类交给线程处理,这个类还是属于主线程,这个函数moveToThread属于QObject方法。

this->moveToThread(thread);

【3】经过上面的处理,须知:本类对象已经属于一个子线程了。thread->start()代表开启线程(开启的是槽线程,不是run线程),线程一开启,可将主线程哪些耗时的操作交给此子线程去处理。
注意:你不能通过在其他类创建本类对象,通过对象调用本类方法去去处理主线程耗时计算。正确的做法是通过发射信号,本子线程会有对应的槽函数去接收处理,在本类槽函数就是一个线程事件循环,在槽函数你可以进行大批量文件文件读写,进行大量的while和for循环的耗时操作,都可以计算完在通过信号发射过给主线程去显示在UI界面。

模拟
主线程 emit sig_read10000lineFile();
子线程 slot_recv10000lineFileData(){慢慢去读取,计算文件行等,不影响主线程做其他任务;}
至于这个文件,你可以创建另一个类,通过在主线程或者子线程创建对象进行文件操作,不过多啰嗦。

在线程槽函数,不管你调用哪些类里面的函数,这些函数已经被列入线程任务了,所以线程ID都会和线程的ID一样。

【4】this->start();这个操作才是真正开启run方法,在这个里面一般使用while或者for循环去判断标志位,处理一些任务,一般在串口通信使用,通过互斥锁、条件变量、信号量等进行超级复杂的操作,俺不喜欢就不介绍了。

thread->start(); //--------------->>>开启槽函数 成员函数等为线程事件
this->start();  //------------>>>>开启run()线程事件

线程的销毁一般在构造函数或者通过信号与槽:

thread->quit();              //已完成的任务退出
thread->wait();              //等待未完成的任务
thread->deleteLater();       //全部完成删除
#include "movetothread4.h"


movetothread4::movetothread4()
{
    thread = new QThread;
    this->moveToThread(thread);
    thread->start(); //--------------->>>开启槽函数 成员函数等为线程事件

    this->start();  //------------>>>>开启run()线程事件

    qDebug ()<<"movetothread4 当前线程ID [构造函数] = "<<QThread::currentThreadId();
    /*
     * movetothread4 当前线程ID =  0x3e90
    */
}

//删除线程
movetothread4::~movetothread4()
{
    thread->quit();              //已完成的任务退出
    thread->wait();              //等待未完成的任务
    thread->deleteLater();   //全部完成删除
}

//movetothread4 [slot_sendfile] 当前线程ID =  0x20e8
// 线程ID与主线程不同,与run()内的线程ID也不同   【新线程1】
void movetothread4::slot_sendfile(QString log)
{
    qDebug ()<<"log = "<<log;

    qDebug ()<<"
 movetothread4 [slot_sendfile] 当前线程ID = "<<QThread::currentThreadId();
}

//movetothread4 [run] 当前线程ID =  0x2f04          【新线程2】
void movetothread4::run()
{
    qDebug ()<<"
 movetothread4 [run] 当前线程ID = "<<QThread::currentThreadId();
}

子线程1对象的创建

子对象记得在主线程创建:有数据需要处理就通过信号与槽建立连接。

#include "mainwindow.h"

#include <QApplication>

#include "movetothread4.h"                /* 第1种线程 此类继承QThread*/

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug ()<<"main 当前线程ID = "<<QThread::currentThreadId();
    qDebug ()<<"main 当前线程地址 = "<<QThread::currentThread()<<endl;

    /*
    main 当前线程ID =  0x3e90
    */

      //创建第1种线程对象 此类继承自QThread,并将其移动到线程(在构造函数执行),特点【run函数是线程事件】【槽函数是线程事件】
    movetothread4 thread_5;
    MainWindow w;

    //关联信号与槽
    QObject::connect(&w,SIGNAL(sig_exec10000(int)),&thread_5,SLOT(recv10000(int)));                                     //槽函数写法1 对象调用

    w.show();
    return a.exec();
}

第二种 子线程2继承自QThread

特点:

  1. 槽函数不属于线程事件,不能处理耗时操作,一般用来设置标志位。
  2. run函数才是线程入口,可以进行耗时操作,通过调用函数或者发射信号实现。
  3. 由于是继承QThread,其他属性根上面差不多,就不细说。
  4. 代码里面有互斥锁,本人还未领悟其真谛,下次用到再写。
  5. 创建第二种线程对象 此类继承自QThread 特点【run函数是线程事件】【槽函数不是线程事件】
qthread_from_QThread thread_2;
thread_2.start(); //开启run

头文件

#ifndef QTHREAD_FROM_QThread_H
#define QTHREAD_FROM_QThread_H

#include <QObject>
#include <QThread>
#include<QDebug>

#include <QMutex>
#include <QMutexLocker>

#include "file.h"

class qthread_from_QThread : public QThread
{
    Q_OBJECT
public:
    explicit qthread_from_QThread();

    void dd();
protected:
    void run() override;
signals:
    void sig_data(int);
    void sig_taskFile(int);
    void sig_sendfile(QString log);
    void sig_Toreadwrite(QString log);


public slots:
    void slot_sendfile(QString log);
    void slot_read(QString log);

private slots:
private:
    file fileobj;
    bool  iswrite = false;

    QMutex mutex;

};

#endif // QTHREAD_FROM_QOBJECT_H

源文件

#include "qthread_from_QTread.h"

//构造函数 还是从属主线程  线程ID和主线程一致  【亲自尝试便知】
qthread_from_QThread::qthread_from_QThread()
{
    qDebug ()<<"qthread_from_QThread -------------->当前线程ID = "<<QThread::currentThreadId();

/* 【 构造函数和主线程相同 】
qthread_from_QThread 当前线程ID =  0x3e90
*/

    connect(this,&qthread_from_QThread::sig_Toreadwrite,this,&qthread_from_QThread::slot_read);
}

//普通成员函数直接调用 线程id和主线程一样 不属于事件线程 【但是本run()调用又属于事件线程】
void qthread_from_QThread::dd()
{
   QMutexLocker lock(&mutex);   //互斥锁无法解决卡屏

    qDebug ()<<"qthread_from_QThread dd -------------->当前线程ID = "<<QThread::currentThreadId();

    //emit this->sig_Toreadwrite(str);          //通过本类发送让槽去读还是会卡屏
    emit this->sig_sendfile(fileobj.readFileToUI());         //[文件读取完毕,直接发送到界面显示]【小于M的文件及时响应处理】
}

//qthread_from_QThread -------------->run当前线程ID =  0x3fc8
// 注意 继承QThread,只有本函数有事件循环 即线程的入口在此
void qthread_from_QThread::run()
{
    qDebug ()<<"qthread_from_QThread -------------->run当前线程ID = "<<QThread::currentThreadId();
    //dd();
    while (1)
    {
        //qDebug() << "do something in run";
        if(iswrite)
        {
            dd();
            iswrite = false;
            this->sleep(1);        //延时无法解决卡屏
        }
    }
}

//不推荐 线程ID与主线程一致    容易卡屏     当前线程ID =  0x3e90
void qthread_from_QThread::slot_sendfile(QString log)
{
    qDebug ()<<"log = "<<log;

    qDebug ()<<"
 qthread_from_QThread [slot_sendfile] -------------->当前线程ID = "<<QThread::currentThreadId();

    //通过这种读取会卡屏  用来设置标志位 【不推荐使用这种】
//    if(log == "log")
//    {
//        QString str = fileobj.readFileToUI();
//        //qDebug ()<<"str = "<<str;
//        emit this->sig_sendfile(str);
//    }
    iswrite = true; //----------------------->>>>>>通过标志位 让run线程去处理
    //dd();     //线程ID如上一样
}

//不推荐 线程ID与主线程一致 容易卡屏    当前线程ID =  0x3e90
void qthread_from_QThread::slot_read(QString log)
{
    QMutexLocker lock(&mutex);

    qDebug ()<<"
 qthread_from_QThread [slot_read] -------------->当前线程ID = "<<QThread::currentThreadId();

    //qDebug ()<<"str = "<<str;
    emit this->sig_sendfile(log);
}



对象创建位置(销毁)

#include "mainwindow.h"

#include <QApplication>

#include "qthread_from_QTread.h"        /* 第二种线程 此类继承QThread*/

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug ()<<"main 当前线程ID = "<<QThread::currentThreadId();
    qDebug ()<<"main 当前线程地址 = "<<QThread::currentThread()<<endl;

    /*
    main 当前线程ID =  0x3e90
    */


    //创建第二种线程对象 此类继承自QThread    特点【run函数是线程事件】【槽函数不是线程事件】
    qthread_from_QThread thread_2;
    thread_2.start();

    MainWindow w;
    w.getThread_1(totalThread);
    w.getThread_2(thread_2);

    //线程2
    QObject::connect(&w,&MainWindow::sig_needsendfile,&thread_2,&qthread_from_QThread::slot_sendfile);  //通知读取文件
QObject::connect(&thread_2,&qthread_from_QThread::sig_sendfile,&w,&MainWindow::slot_recvsendfile);    //槽函数写法1 类名调用

    //线程2释放
    QObject::connect(&thread_2, &qthread_from_QThread::finished, &thread_2, &QObject::deleteLater);       //线程2结束释放工作类
    QObject::connect(&w,&MainWindow::destroyed,&thread_2,&QThread::terminate,Qt::ConnectionType::DirectConnection);   //退出线程2

    w.show();
    return a.exec();
}

第三种 子线程3继承自QThread

特点:

创建第三种线程对象 此类继承自QThread,并将其移动到线程,特点【run函数是线程事件 id=1】【槽函数是线程事件 id=2】【如同开启两个线程】

🙊 🙊区别第一种:线程对象的创建在主线程。(推荐)🙊 🙊

QThread Thread;
qthread_from_QThread2 thread_3;
thread_3.QObject::moveToThread(&Thread);    //如同本类槽变成了线程事件
Thread.start();      //开启信号与槽()
thread_3.start();   // 如同开启run() 

头文件

#ifndef QTHREAD_FROM_QThread2_H
#define QTHREAD_FROM_QThread2_H

#include <QObject>
#include <QThread>
#include<QDebug>

#include <QMutex>
#include <QMutexLocker>

#include "file.h"

class qthread_from_QThread2 : public QThread
{
    Q_OBJECT
public:
    explicit qthread_from_QThread2();

    void dd();
protected:
    void run() override;
signals:
    void sig_sendfile(QString log);

public slots:
    void slot_sendfile(QString log);

private slots:

private:
    file fileobj;
    bool  iswrite = false;

    QMutex mutex;

};

#endif // QTHREAD_FROM_QOBJECT_H

源文件

#include "qthread_from_QTread2.h"

//构造函数 还是从属主线程  线程ID和主线程一致  【亲自尝试便知】
qthread_from_QThread2::qthread_from_QThread2()
{
    qDebug ()<<"qthread_from_QThread2 当前线程ID  -------------->[构造函数] = "<<QThread::currentThreadId();

/* 【 构造函数和主线程相同 】
qthread_from_QThread2 当前线程ID  [构造函数] =  0x3e90
*/
}


//由谁调用,从属谁的线程任务
void qthread_from_QThread2::dd()
{
    qDebug ()<<"qthread_from_QThread2 dd -------------->当前线程ID = "<<QThread::currentThreadId();

    QFile file;
    file.setFileName(FILE1);

    //其他地方打开,等待其他文件处理完
    if(file.isOpen())
        return;

    if(!file.open(QIODevice::ReadOnly))
    {
        qDebug() <<"文件打开失败,原因: "<<file.error();
    }
    qDebug() <<" 文件打开成功 ";

    QString fileContent;
    fileContent.clear();

    QTextStream in(&file);
     iswrite = false;
     emit this->sig_sendfile(in.readAll());
}

// 注意 继承QThread,本函数有事件循环 即线程的入口 由本类对象开启【线程入口id=1】
void qthread_from_QThread2::run()
{
    qDebug ()<<"qthread_from_QThread2--------------> run当前线程ID = "<<QThread::currentThreadId();

    /*
     * qthread_from_QThread2--------------> run当前线程ID =  0x3094
    */
    //dd();
    while (1)
    {
        //qDebug() << "do something in run";
        //QThread::sleep(10);

        if(iswrite)
        {
            dd();
            this->sleep(5);
        }
    }
}

// 线程ID与主线程不同,与run()内的线程ID也不同   【新线程id=2】
void qthread_from_QThread2::slot_sendfile(QString log)
{
    qDebug ()<<"log = "<<log;

    qDebug ()<<"
 qthread_from_QThread2 [slot_sendfile] -------------->当前线程ID = "<<QThread::currentThreadId();

    /*
     * qthread_from_QThread2 [slot_sendfile] 当前线程ID =  0x2d0
   */

    iswrite = true;

   // 读取大文件还是会卡屏
//    if(log == "log")
//    {
//        QString str = fileobj.readFileToUI();
//        this->sleep(1);
//        //qDebug ()<<"str = "<<str;
//        emit this->sig_sendfile(str);
//    }
}




对象的创建

#include "mainwindow.h"

#include <QApplication>

#include "qthread_from_QTread3.h"     

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug ()<<"main 当前线程ID = "<<QThread::currentThreadId();
    qDebug ()<<"main 当前线程地址 = "<<QThread::currentThread()<<endl;

    /*
    main 当前线程ID =  0x3e90
    */

     //创建第三种线程对象 此类继承自QThread,并将其移动到线程,特点【run函数是线程事件 id=1】【槽函数是线程事件 id=2】【如同开启两个线程】
    QThread Thread;
    qthread_from_QThread2 thread_3;
    thread_3.QObject::moveToThread(&Thread);    //如同本类槽变成了线程事件
    Thread.start();      //开启信号与槽()
    thread_3.start();   // 如同开启run() 

    MainWindow w;
    w.show();
    return a.exec();
}

第四种 子线程4继承自QThread

特点:
创建第四种线程对象 此类继承自QThread,并将其移动到线程,特点【run函数是线程事件】【槽函数是线程事件】

  //【在同一线程id run()和槽函数可以各司其职】
    qthread_from_QThread3 thread_4;
    thread_4.start();

注意:下面这句是区别与上面继承的不同写法。

QThread::moveToThread(this); //将本类对象移动到线程

头文件

#ifndef QTHREAD_FROM_QThread3_H
#define QTHREAD_FROM_QThread3_H

#include <QObject>
#include <QThread>
#include<QDebug>

#include <QMutex>
#include <QMutexLocker>

#include "file.h"

class qthread_from_QThread3 : public QThread
{
    Q_OBJECT
public:
    explicit qthread_from_QThread3();

    void dd();
protected:
    void run() override;
signals:
    void sig_sendfile(QString log);

public slots:
    void slot_sendfile(QString log);

private slots:

private:
    file fileobj;
    bool  iswrite = false;

    QMutex mutex;

};

#endif // QTHREAD_FROM_QOBJECT_H

源文件

#include "qthread_from_QTread3.h"

//构造函数 还是从属主线程  线程ID和主线程一致  【亲自尝试便知】
qthread_from_QThread3::qthread_from_QThread3()
{
    qDebug ()<<"qthread_from_QThread3-------------->当前线程ID = "<<QThread::currentThreadId();

    QThread::moveToThread(this); //将本类对象移动到线程

/* 【 构造函数和主线程相同 】
qthread_from_QThread3-------------->当前线程ID =  0x3e90
*/
}


//由谁调用,从属谁的线程ID
void qthread_from_QThread3::dd()
{
    qDebug ()<<"qthread_from_QThread3 dd -------------->当前线程ID = "<<QThread::currentThreadId();
}

// 注意 继承QThread,本函数有事件循环 即线程的入口 由本类对象开启
void qthread_from_QThread3::run()
{
    qDebug ()<<"qthread_from_QThread3 -------------->run当前线程ID = "<<QThread::currentThreadId();
    /*
     * //qthread_from_QThread3 -------------->run当前线程ID =  0x3404
    */

    //qDebug() << "do something in run3";
    //QThread::sleep(10);

    //开启事件循环,否则的话会退出线程
    //不可以将事件循环改成while循环,否则的话槽函数得不到响应

    if(iswrite)
    {
        dd();   //每次只做一次 根据信号触发决定
        iswrite = false;
    }
    exec();
}

// 线程ID与主线程不同,与run()内的线程ID也不同   【新线程】
void qthread_from_QThread3::slot_sendfile(QString log)
{
    qDebug ()<<"log = "<<log;

    qDebug ()<<"
 qthread_from_QThread3 [slot_sendfile] -------------->当前线程ID = "<<QThread::currentThreadId();

    /*
     * qthread_from_QThread3 [slot_sendfile] 当前线程ID =  0x2d0
   */

    iswrite = true;

   // 读取大文件还是会卡屏
//    if(log == "log")
//    {
//        QString str = fileobj.readFileToUI();
//        this->sleep(2);
//        //qDebug ()<<"str = "<<str;
//        emit this->sig_sendfile(str);
//    }
}




对象的创建

在main

//创建第四种线程对象 此类继承自QThread,并将其移动到线程,特点【run函数是线程事件】【槽函数是线程事件】
//【在同一线程id run()和槽函数可以各司其职】
qthread_from_QThread3 thread_4;
thread_4.start();

第五种 子线程5继承自QObject (QT官方主推)

创建第一种线程对象 此类继承自QObject 特点【无run函数】【槽函数是线程事件】

   QThread totalThread;                                 /* 真正的线程栈对象 */
   QThread_from_QObject thread_1;              /* 将此类移动到线程 让线程去处理任务 不影响主线程的运行 */
   thread_1.moveToThread(&totalThread);
   totalThread.start();    //直接开启线程运行 槽线程
//如果想用run就开启
thread_1.start(); //一般不用就可以完成功能

头文件

#ifndef QTHREAD_FROM_QOBJECT_H
#define QTHREAD_FROM_QOBJECT_H

#include <QObject>
#include <QThread>
#include<QDebug>

#include <QMutex>
#include <QMutexLocker>
#include <string>
#include <cstdlib>
#include <cstdio>

#include "file.h"

class QThread_from_QObject : public QObject
{
    Q_OBJECT
public:
    explicit QThread_from_QObject(QObject *parent = nullptr);

    void dd();

signals:
    void sig_data(int);
    void sig_taskFile(int);
    void sig_sendfile(QString log);

public slots:
    void  recv10000(int cont);
    void slot_sendfile(QString log);

private slots:
    void  slot_taskFile(int indedx);

private:
    file fileobj;

};

#endif // QTHREAD_FROM_QOBJECT_H

源文件

#include "qthread_from_qobject.h"

/* 本类所有槽函数和信号都是属于线程任务之一,线程ID与主线程截然不同
QThread_from_QObject 当前线程ID  [构造函数] =  0x3e90
*/

//构造函数 还是从属主线程  线程ID和主线程一致  【亲自尝试便知】
QThread_from_QObject::QThread_from_QObject(QObject *parent) : QObject(parent)
{
    qDebug ()<<"QThread_from_QObject 当前线程ID  [构造函数] = "<<QThread::currentThreadId();

    /* 【 构造函数和主线程相同 】
     * QThread_from_QObject 当前线程ID =  0x3e90
    */

    connect(this,&QThread_from_QObject::sig_taskFile,this,&QThread_from_QObject::slot_taskFile);
}

//普通成员函数直接调用 线程id和主线程一样 不属于事件线程 【但是本线程槽函数调用又属于事件线程】
void QThread_from_QObject::dd()
{
    qDebug ()<<"dd 当前线程ID = "<<QThread::currentThreadId();
}

// 【这种线程的特点:每一个槽函数都是一个事件(如同run()函数) 对于处理文件读写等推荐使用】
//  槽函数的线程ID和主线程不同 说明开启子线程成功    【亲自尝试便知】
void QThread_from_QObject::recv10000(int cont)
{
    /* 通过互斥锁 还是会让主线程卡屏 【行不通】 */
    //static QMutex mutex;
    //QMutexLocker lock(&mutex);

    qDebug ()<<"recv10000 当前线程ID = "<<QThread::currentThreadId();

    for( int i=0;i<cont;++i)
    {
        qDebug () <<" i = "<<i;
        emit this->sig_data(i);            //显示在UI界面
        emit this->sig_taskFile(i);       //写入文件后在显示在UI界面
        QThread::msleep(10);                  //10ms缓冲时间【没有这句 数据很大必然会卡】
    }
}

//槽 读取文件到UI 【只要在槽函数调用的函数 不管什么函数 线程ID一样 即工作在线程任务】
//【此类不断写文件】在这里读取无法获取文件内容【交给线程2去做】
void QThread_from_QObject::slot_sendfile(QString log)
{
    Q_UNUSED(log);
#if 0
    qDebug ()<<"
 QThread_from_QObject [slot_sendfile] 当前线程ID = "<<QThread::currentThreadId();
    qDebug ()<<"QThread_from_QObject [slot_sendfile] 当前线程地址 = "<<QThread::currentThread()<<endl;

    if(log == "log")
    {
        QString str = fileobj.readFileToUI();
        //qDebug ()<<"str = "<<str;
        emit this->sig_sendfile(str);
    }

#endif
}

//槽 写入文件 【只要在槽函数调用的函数 不管什么函数 线程ID一样 即工作在线程任务】
void QThread_from_QObject::slot_taskFile(int indedx)
{
    qDebug ()<<"
 slot_taskFile 当前线程ID = "<<QThread::currentThreadId();

    /*
     * slot_taskFile 当前线程ID =  0x1440
     * slot_taskFile 当前线程地址 =  QThread(0x8bfc80)
    */

    fileobj.writeToFile(QString::number(indedx));   //线程ID如上一样

    //dd();     //线程ID如上一样
}



对象的创建 (销毁)

#include "mainwindow.h"
#include <QApplication>

#include "qthread_from_qobject.h"       /* 第一种线程 此类继承QObject*/

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug ()<<"main 当前线程ID = "<<QThread::currentThreadId();
    qDebug ()<<"main 当前线程地址 = "<<QThread::currentThread()<<endl;

    /*
    main 当前线程ID =  0x3e90
    */

    //创建第一种线程对象 此类继承自QObject    特点【无run函数】【槽函数是线程事件】
    QThread totalThread;                                 /* 真正的线程栈对象 */
    QThread_from_QObject thread_1;              /* 将此类移动到线程 让线程去处理任务 不影响主线程的运行 */
    thread_1.moveToThread(&totalThread);
    totalThread.start();    //直接开启线程运行

    MainWindow w;
    w.getThread_1(totalThread);
    //关联信号与槽
    QObject::connect(&w,SIGNAL(sig_exec10000(int)),&thread_1,SLOT(recv10000(int)));                                     //槽函数写法1 对象调用


QObject::connect(&thread_1,&QThread_from_QObject::sig_sendfile,&w,&MainWindow::slot_recvsendfile);  //读取文件显示UI

    //线程1
    QObject::connect(&w,&MainWindow::sig_needsendfile,&thread_1,&QThread_from_QObject::slot_sendfile);   //通知读取文件
    QObject::connect(&thread_1,&QThread_from_QObject::sig_data,&w,&MainWindow::slot_showUI);                //槽函数写法1 类名调用


    w.show();
    return a.exec();
}

Qt官方多线程使用截图(2种)

第一种继承QObject

我的项目采用这种方法。
在这里插入图片描述

第二种继承QThread

在这里插入图片描述

信号与槽QObject::connect的第五个参数(多线程)

实际没用上,意义不大,用错还会导致很多问题。因为默认这个就已经够了。
在这里插入图片描述

主界面源码

mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>
#include<QDebug>

#include <QDateTime>
#include <QTimer>
#include <QtConcurrent>
#include <QFuture>

#include <QTimer>

#include "file.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void getThread_1(QThread &obj);
    void getThread_2(QThread &obj);

public slots:
    void  slot_showUI(int data); //共有槽 其他.cpp可使用
    void  slot_recvsendfile(QString Log);  //接收信号读取文件并转发

private slots:
    void on_pushButton_clicked();   //私有槽 只有当前.cpp可使用
    void on_pushButton_3_clicked();
    void on_pushButton_4_clicked();
    void showtime();
    void on_pushButton_2_pressed();

signals:
    void  sig_exec10000(int);
    void  sig_needsendfile(QString);

private:
    Ui::MainWindow *ui;

    file fileobj; //本头文件.h先编译  在编译本.cpp 【编译头文件时 file类构造函数被执行】

    QString Log;
    bool  stop = false;

    QThread *mythread1 ;
    QThread *mythread2 ;

    QTimer time;
    QTimer readTime;

};
#endif // MAINWINDOW_H

mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

/*
 * MainWindow 当前线程ID =  0x3e90
*/
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug ()<<"MainWindow 当前线程ID = "<<QThread::currentThreadId();

    connect(&time,&QTimer::timeout,this,&MainWindow::showtime);
    time.start(1000);
}

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

void MainWindow::getThread_1(QThread &obj)
{
    this->mythread1 = &obj;
}

void MainWindow::getThread_2(QThread &obj)
{
    this->mythread2 = &obj;
}

//发送10000个数据给线程1处理 在主线程处理会(无响应)
void MainWindow::on_pushButton_clicked()
{
    if(stop == true)
        return;
        emit this->sig_exec10000(10000);
        //ui->textBrowser->append(QString::number(i)); //数据越大 界面无法刷新 且会处于假死 直到任务完成【槽函数最好不要有循环】
}

//上面刷新数据时 界面会卡死 【通过让线程发射数据过来还是会卡死】
//【在发送信号的位置下面添加线程延时可解决卡屏 延时多少就看你的任务代码有多大了】
void MainWindow::slot_showUI(int data)
{
    ui->textBrowser->append(QString::number(data));
}

void MainWindow::slot_recvsendfile(QString Log)
{
     ui->textBrowser_2->append(Log);
    //qApp->processEvents(QEventLoop::ExcludeUserInputEvents); //无法解决卡屏
}

//不断读文件 显示文件内容 【容易卡屏】
void MainWindow::on_pushButton_2_pressed()
{
#if 0
    QString fileText = fileobj.readFileToUI();  // 这种方式会卡屏
    if(fileText.isEmpty())
        return;
    qDebug ()<<" 读取文件成功 ";
    ui->textBrowser_2->append(fileText);
#elif (1)   //通过发射信号 让线程处理了放在变量里面 通过变量显示在UI----》此操作任然会卡屏
        qDebug() <<"==============读取文件==============";
        emit this->sig_needsendfile("log");
#elif (0)//通过创建临时线程去做耗时的任务 ----》此操作任然会卡屏
    QFuture<QString>future = QtConcurrent::run(&fileobj,&file::readFileToUI);
    //获取线程中执行函数返回的结果
    QString testres = future.result();      //[QFuture :: result()函数会阻塞线程并等待结果可用]
    qDebug() << "future:" << testres ;
    if(!future.isFinished()){               //判断线程是否执行完成
            future.waitForFinished();   //等待线程执行完
     }
     qDebug() << "future:" << future.isFinished();//true
     //ui->textBrowser_2->append(testres);
#endif
}

void MainWindow::on_pushButton_3_clicked()
{
    qDebug ()<<"on_pushButton_3_clicked 当前线程ID = "<<mythread1->currentThreadId();

    if(mythread1->isRunning())
        qDebug() <<"线程1在运行";
    if(mythread1->isFinished())
        qDebug() <<"线程1任务完成";

    if(mythread2->isRunning())
        qDebug() <<"线程2在运行";
    if(mythread2->isFinished())
        qDebug() <<"线程2任务完成";
}

void MainWindow::on_pushButton_4_clicked()
{
    ui->textBrowser->clear();
    ui->textBrowser_2->clear();
}

//系统时间
void MainWindow::showtime()
{
    ui->statusbar->showMessage(QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss"));
}



UI界面设计

在这里插入图片描述


🙈 🙈同志们再见!!!🙈 🙈


风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。