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

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

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

QT多线程5种用法

  • 第一种 主线程(GUI)
  • 第二种 子线程1继承自QThread
    • 头文件 movetothread4.h
    • 源文件 movetothread4.cpp
    • 子线程1对象的创建
  • 第二种 子线程2继承自QThread
    • 头文件
    • 源文件
    • 对象创建位置(销毁)
  • 第三种 子线程3继承自QThread
    • 头文件
    • 源文件
    • 对象的创建
  • 第四种 子线程4继承自QThread
    • 头文件
    • 源文件
    • 对象的创建
  • 第五种 子线程5继承自QObject (QT官方主推)
  • 头文件
    • 源文件
  • 对象的创建 (销毁)
  • Qt官方多线程使用截图(2种)
    • 第一种继承QObject
    • 第二种继承QThread
  • 信号与槽QObject::connect的第五个参数(多线程)
  • 主界面源码
  • 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 ()<<"\\r\\n movetothread4 [slot_sendfile] 当前线程ID = "<<QThread::currentThreadId();
}//movetothread4 [run] 当前线程ID =  0x2f04          【新线程2】
void movetothread4::run()
{qDebug ()<<"\\r\\n 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 ()<<"\\r\\n 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 ()<<"\\r\\n 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);//线程2QObject::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);   //退出线程2w.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 ()<<"\\r\\n 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 ()<<"\\r\\n 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 0qDebug ()<<"\\r\\n 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 ()<<"\\r\\n 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//线程1QObject::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

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

第二种继承QThread

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

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

实际没用上,意义不大,用错还会导致很多问题。因为默认这个就已经够了。
QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。

主界面源码

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_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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 0QString 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界面设计

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


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