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

news2024/12/27 13:59:35

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);

    //线程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界面设计

在这里插入图片描述


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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/420914.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于matlab仿真机械手

一、前言该示例显示了处于主动立体视觉模式的操纵器。它说明了立体渲染属性的效果以及如何使用立体视觉 VRFIGURE 属性。仅当图形卡支持四缓冲 OpenGL 渲染并由图形卡驱动程序启用时&#xff0c;操纵器才会以活动立体视觉模式显示。请注意&#xff0c;只有当您使用带有主动快门…

Android 布局 Fragment

Android 布局 FragmentFragment出现的初衷生命周期onCreate()onPause()onAttach()onCreateView()onActivityCreated()onDestroyView()onDetach()您可能还想扩展几个子类&#xff0c;而非 Fragment 基类&#xff1a;DialogFragmentListFragmentPreferenceFragmentCompat同系列文…

2.3.2单链表的插入删除

按位序插入&#xff08;带头结点&#xff09; 将第i-1个结点的指针指向第i个结点。 头节点看作是第0个结点。 s->datae //设定s指针的数据域为e s->nextp->next //将p指针指向的位置赋值给s指针指向的位置 p->nexts //再将s的数据域赋值给p指针指向的位置…

浅析分布式理论的CAP

大家好&#xff0c;我是易安&#xff01; 今天让我们来聚焦于分布式系统架构中的重要理论——CAP理论。在分布式系统中&#xff0c;可用性和数据一致性是两个至关重要的因素&#xff0c;而CAP理论就是在这两者之间提供了一种权衡的原则&#xff0c;帮助我们在设计分布式系统时进…

MATLAB基于BP神经网络的光伏发电太阳辐照度预测(源码链接在文末)

光伏发电功率主要受太阳辐照度影响&#xff0c;所以准确预测太阳辐照度对光伏功率预测十分重要。程序采用小波分解先对辐照度数据进行分解&#xff0c;然后再用bp神经网络对分解的辐照度数据分别预测&#xff0c;再组合作为最后的预测结果。 人工神经网络(Artificial Neural …

研0进阶式学习---数据库1

目录Excel和数据库数据库的分类数据库的举例数据库基本结构数据库管理系统xampp、Navicat、MySQL基础知识与界面展示数据库基本连接步骤Excel和数据库 Excel和数据库都是用来存储数据的工具&#xff0c;但它们有以下区别和联系&#xff1a; 区别&#xff1a; 数据类型&#…

Java实现内网穿透

使用场景 1、当公司的一些系统功能使用了第三方服务时&#xff0c;通常第三方会回调我们的接口。在对接阶段&#xff0c;为了方便debug&#xff0c;我们肯定希望能回调到我们本地电脑上来。 2、当你在公司想访问部署在家里电脑的服务或者文件时。 3、当你的外地同事想访问你…

搭建nginx反向代理实现动静态分离

搭建nginx反向代理实现动静态分离一、实现高可用动静分离二 实验步骤2.1 安装nginx和开启路由转发2.2 实现lvs负载均衡2.3 实现动态网页功能2.4 实现nginx反向代理2.5 重启服务网页查看一、实现高可用动静分离 1、部署目的 ①用户访问业务时访问虚拟ip由lvs负责转发请求到业务…

LAMP及论坛搭建

1.编译安装apache 关闭防火墙&#xff0c;将安装Apache所需软件包传到/opt目录下 systemctl stop firewalld.service setenforce 0[rootlocalhost opt]# lsapr-1.6.2.tar.gz apr-util-1.6.0.tar.gz httpd-2.4.29.tar.bz2安装环境依赖包 yum -y install gcc gcc-c make pc…

淘宝商品数据分析怎么操作?从哪些方面下手?

说起淘宝上面的商品可谓是成千上万&#xff0c;因此要是想对这些商品进行数据分析的话&#xff0c;也是需要一些技能的&#xff0c;并不是所有的数据都特别重要&#xff0c;我们需要学会选择比较重要的数据来进行分析。详细内容还请各位接着往下看! 一、根据淘宝指数分析以下相…

【JSP学习笔记】2.JSP 结构、生命周期及语法

前言 本章介绍JSP的结构、生命周期及语法。 JSP 结构 网络服务器需要一个 JSP 引擎&#xff0c;也就是一个容器来处理 JSP 页面。容器负责截获对 JSP 页面的请求。本教程使用内嵌 JSP 容器的 Apache 来支持 JSP 开发。 JSP 容器与 Web 服务器协同合作&#xff0c;为JSP的正…

springboot读取yml文件中的list列表、数组、map集合和对象方法实例

目录前言application.yml定义list集合application.yml定义数组类型总结 前言 springboot配置文件yml类型简单的风格&#xff0c;十分受大家的欢迎&#xff0c;支持字符string类型&#xff0c;支持列表list类型&#xff0c;支持集合map类型&#xff0c;支持数组array类型&#x…

MySQL数据库 - 基础篇

本文文章基于黑马《MySQL》课程所做的笔记 1、基础篇 1.1、MySQL概述 数据库相关概念 名称全称简介数据库存储数据的仓库&#xff0c;数据是有组织的进行存储DataBase(DB)数据库管理系统操纵和管理数据库的大型软件DataBase Management System(DBMS)SQL操作关系型数据库的编程…

ijkplayer 编译增加支持更多的音视频格式

ijkplayer是B站开源的一款基于ffmpeg的移动端播放器。但为了减少播放器的体积&#xff0c;很多音视频的格式播放默认都是不支持的&#xff0c;需要自己下载ijkplayer源码进行编译。这里以mac环境下android为例&#xff0c;简述ijkplayer的编译过程&#xff0c;以及为了支持更多…

最新版本 Stable Diffusion 开源 AI 绘画工具之图生图进阶篇

✨ 目录&#x1f388; 图生图基本参数&#x1f388; 图生图&#xff08;img2img&#xff09;&#x1f388; 涂鸦绘制&#xff08;Sketch&#xff09;&#x1f388; 局部绘制&#xff08;Inpaint&#xff09;&#x1f388; 涂鸦蒙版&#xff08;Inpaint sketch&#xff09;&…

Adams2020软件安装教程

目录 一、软件简介 二、软件下载 三、软件安装 一、软件简介 Adams是一款多领域仿真软件&#xff0c;可以对机械、电子、流体、化学等系统进行建模、仿真和分析。Adams具有强大的动力学分析功能&#xff0c;可以模拟多种复杂系统的运动、力学、热力学和控制行为&#xff0c;包…

点云目标检测:open3d多窗口联动可视化

多个模型之间的对比除了指标之外,还需要可以直观的对比可视化结果,这次介绍一下如何使用open3d同时打开两个窗口分别加载两个模型的结果,同时实现两个窗口的联动,也就是当一个窗口拖动或者缩放时另外一个窗口也跟着做同样的变换。 点云可视化:使用open3d实现点云连续播放这…

nginx虚拟主机

一、基于域名的nginx虚拟主机 1、基于域名的nginx虚拟主机的操作步骤 1.1 为虚拟主机提供域名和IP的映射(也可以使用DNS正向解析) echo "192.168.2.66 www.xkq.com www.wy.com" >> /etc/hosts1.2 为虚拟主机准备网页文档 mkdir -p /var/www/html/ly/ mkdir…

第一章 深度学习入门之流程初体验

效果图 文章中采用的是棋盘格数据&#xff0c;这张图里面用的是开源的模型&#xff0c;可以用于测试&#xff0c;此图为最终效果&#xff0c;加载mp4视频&#xff0c;通过opencv读取图像&#xff0c;传递到infer接口&#xff0c;进行推理识别&#xff0c;利用opencv显示出来 …

比较好的文档翻译软件-哪个翻译软件最精准

免费语言翻译软件可以帮助用户快速准确地翻译语言&#xff0c;其主要作用如下&#xff1a; 辅助语言学习&#xff1a;语言翻译软件可以帮助用户学习语言&#xff0c;让用户更好地理解在不同语言之间的差异&#xff0c;从而使语言学习更加高效。 翻译外语内容&#xff1a;语言翻…