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都是线程事件,可以进行耗时操作
👮 👮特点:👮 👮
- 自己创建的类需要继承 QThread 类
- 需要重写父类run方法(线程事件入口):void run() override;
- 在主线程创建线程对象,至于在那些主线程类来创建由你自己定。
- 自己继承线程的这个类一定不能指定父对象。
- 凡是自己创建的线程类,一定不能在本类里面操作UI界面组件,一般通过信号与槽与主线程进行交互。
- 线程完成任务,记得释放内存,QT官方规定。
- 创建第1种线程对象 此类继承自QThread,并将其移动到线程(在构造函数执行),特点【run函数是线程事件】【槽函数是线程事件】
movetothread4 thread_5;
🙉 🙉 下面以代码来讲解🙉 🙉
头文件 movetothread4.h
讲解:这是我自己创建的类,继承QThread,头文件需要添加:
- #include “QObject” ------》》》继承祖类,可以使用信号与槽机制
- #include “QThread” ------》》》线程头必加
- #include"QDebug" ------》》》打印
- void run() override; 可以写在私有、保存、共有无妨。
- 线程对象在当前类创建,这是区别其他继承的不同点。
#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
特点:
- 槽函数不属于线程事件,不能处理耗时操作,一般用来设置标志位。
- run函数才是线程入口,可以进行耗时操作,通过调用函数或者发射信号实现。
- 由于是继承QThread,其他属性根上面差不多,就不细说。
- 代码里面有互斥锁,本人还未领悟其真谛,下次用到再写。
- 创建第二种线程对象 此类继承自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
我的项目采用这种方法。
第二种继承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_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界面设计
🙈 🙈同志们再见!!!🙈 🙈