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);
//线程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 ()<<"\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 0
qDebug ()<<"\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
//线程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界面设计
🙈 🙈同志们再见!!!🙈 🙈