Qt——多线程中的信号与槽(二)

1.上节实验发现:当槽函数是线程类中的成员时,为什么依然不在本线程内被调用执行?

2.隐藏的问题:

  • 对象依附于哪一个线程?
    • 默认情况下,对象依附于自身被创建的线程;例如:对象在主线程(main()函数)中被创建,则依附于主线程
  • 对象的依附性与槽函数执行的关系
    • 默认情况下,槽函数在其所依附的线程中被调用执行
  • 对象的依附性是否可以改变
    • QObject::moveToThread用于改变对象的线程依附性,使得对象的槽函数在依附的线程中被调用执行

MyObject.h

复制代码
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = nullptr);
    
signals:
    
protected slots:
    void getStarted();
    void testSlot();
};

#endif // MYOBJECT_H

MyObject.cpp

复制代码
#include "MyObject.h"
#include <QThread>
#include <QDebug>

MyObject::MyObject(QObject *parent)
    : QObject{parent}
{
    
}
void MyObject::getStarted()
{
    qDebug() << "void MyObject::getStarted() tid = " << QThread::currentThreadId();
}
void MyObject::testSlot()
{
    qDebug() << "void MyObject::testSlot() tid = " << QThread::currentThreadId();
}

TestThread.h

复制代码
#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>

class TestThread : public QThread
{
    Q_OBJECT
    
protected:
    void run();
public:
    explicit TestThread(QObject *parent = nullptr);
    
signals:
    void testSignal();
    
protected slots:
    void testSlot();
};

#endif // TESTTHREAD_H

TestThread.cpp

复制代码
#include "TestThread.h"
#include <QDebug>

TestThread::TestThread(QObject *parent)
    : QThread{parent}
{
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));
}
void TestThread::run()
{
    qDebug() << "void TestThread::run() --- --- begin tid = " << QThread::currentThreadId();
    for(int i=0; i<5; i++)
    {
        qDebug() << "void TestThread::run() i" <<i;
        sleep(1);
    }
    emit testSignal();
    qDebug() << "void TestThread::run() --- --- end";
}
void TestThread::testSlot()
{
    qDebug() << "void TestThread::testSlot() tid = " << QThread::currentThreadId();
}

main.cpp

复制代码
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    qDebug() << "main() tid = " << QThread::currentThreadId();
    
    TestThread t;
    MyObject m;
    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));
    
    //m.moveToThread(&t); //改变m线程的依附性,依附于t
    t.start();
    return QCoreApplication::exec();
}

运行结果:

main() tid = 0x30b8

void TestThread::run() --- --- begin tid = 0x1b78

void TestThread::run() i 0

void MyObject::getStarted() tid = 0x30b8

void TestThread::run() i 1

void TestThread::run() i 2

void TestThread::run() i 3

void TestThread::run() i 4

void TestThread::run() --- --- end

void TestThread::testSlot() tid = 0x30b8

void MyObject::testSlot() tid = 0x30b8

将main.cpp中的m.moveToThread(&t);取消注释后的运行结果:

  1. 主线程 创建 TestThread tMyObject m

  2. 连接信号槽:

    • tstarted()mgetStarted()(跨线程)

    • ttestSignal()mtestSlot()(跨线程)

    • ttestSignal()ttestSlot()同一对象,同一线程

  3. m.moveToThread(&t)m 移到子线程

  4. t.start() → 启动子线程

main() tid = 0x3b40

void TestThread::run() --- --- begin tid = 0x16e4

void TestThread::run() i 0

void TestThread::run() i 1

void TestThread::run() i 2

void TestThread::run() i 3

void TestThread::run() i 4

void TestThread::run() --- --- end

void TestThread::testSlot() tid = 0x3b40

我们发现,改变m的依附性后,m的槽函数没有被执行

3.线程中的事件循环

  • 信号与槽的机制需要事件循环的支持
  • QThread类中提供的exec()函数用于开启线程的事件循环
  • 只有事件循环开启,槽函数才能在信号发送后被调用

所以:前提条件:对象依附的线程开启了事件循环,后置结果:对象中的槽函数在依附的线程中被调用执行

我们将TestThread.cpp中的run()函数中加入exec();语句后,并且将t的依附性改为t的结果是:

main() tid = 0x330

void MyObject::getStarted() tid = 0x6ec

void TestThread::run() --- --- begin tid = 0x6ec

void TestThread::run() i 0

void TestThread::run() i 1

void TestThread::run() i 2

void TestThread::run() i 3

void TestThread::run() i 4

void TestThread::testSlot() tid = 0x6ec

void MyObject::testSlot() tid = 0x6ec

研究这个的意义在于:当信号的发送与对应槽函数的执行在不同线程中,可能产生临界资源的竞争问题

4.但是上述代码加入exec()后,开启事件循环,我们发现无法exec()后面的代码无法执行,那么线程如何正常结束?

  • 事件循环结束前,exec()后的语句无法执行
  • quit()和exit()函数用于结束事件循环
  • quit()<------>exit(0),exec()的返回值由exit()参数决定

无论事件循环是否开启,信号发送后会直接进入对象所依附的事件队列;然而只有开启了事件循环,对应的槽函数才会在线程中被调用

所以解决方案是在main.cpp中加入

t.wait(8 * 1000);

t.quit();

即:等待8秒,如果线程还没有结束,就向下执行

5.什么时候需要在线程中开启事件循环?

事务性操作(间断性IO操作等)可以开启线程的事件循环,每次操作通过发送信号的方式使得槽函数在子线程中执行

示例:写入文件

FileWriter.cpp

复制代码
#ifndef FILEWRITER_H
#define FILEWRITER_H

#include <QFile>
#include <QObject>
#include <QThread>

class FileWriter : public QObject
{
    Q_OBJECT

    class Worker : public QThread
    {
    protected:
        void run();
    };

    QFile m_file;
    Worker m_worker;
public:
    explicit FileWriter(QString file, QObject* parent = 0);
    bool open();
    void write(QString text);
    void close();
    ~FileWriter();
signals:
    void doWrite(QString text);
    void doClose();
protected slots:
    void writeSlot(QString text);
    void closeSlot();
};

#endif // FILEWRITER_H

FileWriter.h

复制代码
#include "FileWriter.h"
#include <QDebug>
#include <QString>
void FileWriter::Worker::run()
{
    qDebug() << "void FileWriter::Worker::run() - begin: tid = " << currentThreadId();
    exec();
    qDebug() << "void FileWriter::Worker::run() - end";
}
FileWriter::FileWriter(QString file, QObject* parent) :
    QObject(parent), m_file(file)
{
    connect(this, SIGNAL(doWrite(QString)), this, SLOT(writeSlot(QString)));
    connect(this, SIGNAL(doClose()), this, SLOT(closeSlot()));
    moveToThread(&m_worker);
    m_worker.start();
}

bool FileWriter::open()
{
    return m_file.open(QIODevice::WriteOnly | QIODevice::Text);
}
void FileWriter::write(QString text)
{
    qDebug() << "void FileWriter::write(QString text) tid = " << QThread::currentThreadId();
    emit doWrite(text);
}
void FileWriter::close()
{
    qDebug() << "void FileWriter::close() tid = " << QThread::currentThreadId();
    emit doClose();
}
void FileWriter::writeSlot(QString text)
{
    qDebug() << "void FileWriter::writeSlot(QString text) tid = " << QThread::currentThreadId();
    m_file.write(text.toLatin1());
    m_file.flush(); //强制将缓冲区中的数据立即写入物理存储设备,所以write函数是一个很低效的事务
}
void FileWriter::closeSlot()
{
    qDebug() << "void FileWriter::closeSlot() tid = " << QThread::currentThreadId();
    m_file.close();
}
FileWriter::~FileWriter()
{
    m_worker.quit();
}

main.cpp

复制代码
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include "FileWriter.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();
    FileWriter writer("C:\\Users\\聪\\Desktop\\1.txt");
    if( writer.open() )
    {
        writer.write("hello");
        writer.write("11");
        writer.write("22");
        writer.close();
    }

    return QCoreApplication::exec();
}

运行结果:

main() tid = 0x9d0

void FileWriter::Worker::run() - begin: tid = 0x2108

void FileWriter::write(QString text) tid = 0x9d0

void FileWriter::write(QString text) tid = 0x9d0

void FileWriter::write(QString text) tid = 0x9d0

void FileWriter::close() tid = 0x9d0

void FileWriter::writeSlot(QString text) tid = 0x2108

void FileWriter::writeSlot(QString text) tid = 0x2108

void FileWriter::writeSlot(QString text) tid = 0x2108

void FileWriter::closeSlot() tid = 0x2108

相关推荐
Quz1 天前
QML Label 完整用法与 Text 组件选型
前端·qt
kaixin_learn_qt_ing1 天前
QPluginLoader
qt
kaixin_learn_qt_ing1 天前
Qt 提升部件(Promote)找不到头文件路径
qt
库克克1 天前
【C++】类和对象--类对象模型与大小计算
开发语言·jvm·c++
嘘嘘出差1 天前
天气数据监控系统
jvm·爬虫
2601_949817722 天前
C++指针与引用深度精讲:底层原理、差异对比与高阶实战陷阱
java·jvm·c++
秋田君2 天前
QT_QT布局详解
开发语言·数据库·qt
辞旧 lekkk2 天前
【Qt系统相关】鼠标事件
linux·开发语言·qt·学习·计算机外设·萌新