在Qt中使用多线程时的事件循环和信号槽处理

Event loops and signal-slot processing when using multithreading in Qt

本文关键字:循环 事件 信号 处理 Qt 多线程      更新时间:2023-10-16

我在使用QThreads时遇到了一些问题,这使我在找到合适的组合之前探索了不同的组合。但是,当涉及到事件循环和信号时隙处理时,我仍然不完全理解下面显示的四种情况下到底发生了什么。

我在 OUTPUT 部分添加了一些注释,但正如您所看到的,我不确定我对导致观察到的行为的原因的假设是否正确。我也不确定case 3是否可以在实际代码中使用。这是我的测试代码(每种情况只有main.cpp不同):

工人.h:

#include <QObject>
#include <QDebug>
#include <QThread>
class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = 0) { this->isRunning_ = false;}
    bool isRunning() const { return isRunning_; }
signals:
    void processingFinished();
    void inProgress();
public slots:
    void process()
    {
        this->isRunning_ = true;
        qDebug() << this << "processing started";
        for (int i = 0; i < 5; i++)
        {
            QThread::usleep(1000);
            emit this->inProgress();
        }
        qDebug() << this << "processing finished";
        this->isRunning_ = false;
        emit this->processingFinished();
    }
private:
    bool isRunning_;
};

工人经理.h:

#include "worker.h"
class WorkerManager : public QObject
{
    Q_OBJECT
public:
    explicit WorkerManager(QObject *parent = 0) :
        QObject(parent) {}
public slots:
    void process()
    {
        QThread *thread = new QThread();
        Worker  *worker = new Worker();
        connect(thread,SIGNAL(started()),worker,SLOT(process()));
        connect(worker,SIGNAL(processingFinished()),this,SLOT(slot1()));
        connect(worker,SIGNAL(inProgress()),this,SLOT(slot2()));
        worker->moveToThread(thread);
        qDebug() << "starting";
        thread->start();
        QThread::usleep(500);
        while(worker->isRunning()) { }
        qDebug() << "finished";
    }
    void slot1() { qDebug() << "slot1"; }
    void slot2() { qDebug() << "slot2"; }
};

main.cpp(案例 1 - 没有单独的线程用于workerManager):

#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    WorkerManager* workerManager = new WorkerManager;    
    workerManager->process();
    qDebug() << "end";
    return a.exec();
}

输出 - slot1slot2a.exec()调用(??? -使用主事件循环?

starting 
Worker(0x112db20) processing started 
Worker(0x112db20) processing finished 
finished 
end
slot2 
slot2 
slot2 
slot2 
slot2 
slot1 

main.cpp(案例 2 - workerManager移动到单独的线程,但线程未启动):

#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    WorkerManager* workerManager = new WorkerManager;
    QThread *thread = new QThread();   
    workerManager->moveToThread(thread);       
    workerManager->process();
    qDebug() << "end";
    return a.exec();
}

输出 - 既没有调用slot1也没有调用slot2 - (???与线程关联的事件循环接收信号,但由于线程未启动,因此不调用插槽?

starting 
Worker(0x112db20) processing started 
Worker(0x112db20) processing finished 
finished 
end

main.cpp(情况 3 - workerManager移动到单独的线程,线程已启动,但workerManager::process()通过 workerManager->process() 调用):

#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    WorkerManager* workerManager = new WorkerManager;
    QThread *thread = new QThread();   
    workerManager->moveToThread(thread); 
    thread->start();     
    workerManager->process();
    qDebug() << "end";
    return a.exec();
}

输出 - Worker仍在执行其process()时调用slot2 (???):

starting 
Worker(0x197bb20) processing started 
slot2 
slot2 
slot2 
slot2 
Worker(0x197bb20) processing finished 
finished 
end 
slot2 
slot1 

main.cpp(情况 4 - workerManager移动到单独的线程,线程启动但workerManager::process()使用来自thread的信号调用started()):

#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    WorkerManager* workerManager = new WorkerManager;
    QThread *thread = new QThread();    
    workerManager->moveToThread(thread);
    QObject::connect(thread,SIGNAL(started()),workerManager,SLOT(process()));
    thread->start();
    qDebug() << "end";
    return a.exec();
}

输出 - 达到a.exec() (???) 后处理的所有事件:

end 
starting 
Worker(0x7f1d700013d0) processing started 
Worker(0x7f1d700013d0) processing finished 
finished 
slot2 
slot2 
slot2 
slot2 
slot2 
slot1 

感谢您的任何澄清。

你得到的所有结果都是完全正确的。我将尝试解释这是如何工作的。

事件循环

是Qt代码中的一个内部循环,用于处理系统和用户事件。主线程的事件循环在调用a.exec()时启动。另一个线程的事件循环由 QThread::run 的默认实现启动。

当Qt决定处理事件时,它会执行其事件处理程序。当事件处理程序工作时,Qt没有机会处理任何其他事件(除非通过QApplication::processEvents()或其他方法直接给出)。事件处理程序完成后,控制流返回到事件循环,Qt可能会执行另一个处理程序来处理另一个事件。

信号和时隙与Qt术语中的事件和事件处理程序不同。但是槽由事件循环处理的方式有些相似。如果您的代码中有控制流(例如在main函数中),您可以立即执行任何插槽,就像任何其他C++函数一样。但是当Qt这样做时,它只能从事件循环中做到这一点。应该注意的是,信号总是立即发送,而时隙执行可能会延迟。

现在让我们看看每种情况下会发生什么。

案例1

WorkerManager::process直接在程序启动时执行。新线程将启动,Worker::process 将立即在新线程中执行。 WorkerManager::process继续执行,直到 Worker 完成,冻结主线程中的所有其他操作(包括槽处理)。WorkerManager::process完成后,控制流将转到 QApplication::exec 。Qt建立与其他线程的连接,接收有关时隙调用的消息,并因此调用所有消息。

案例2

默认情况下,Qt在该对象所属的线程中执行对象的插槽。主线程不会执行WorkerManager插槽,因为它属于另一个线程。但是,此线程永远不会启动。它的事件循环永远不会完成。slot1slot2的调用永远留在Qt的队列中,等待你启动线程。悲伤的故事。

案例3

在这种情况下,WorkerManager::process在主线程中执行,因为您直接从主线程调用它。同时,WorkerManager 的线程启动。其事件循环启动并等待事件。 WorkerManager::process启动Worker 的线程并在其中执行Worker::execWorker开始向WorkerManager发送信号。 WorkerManager的线程几乎立即开始执行适当的插槽。在这一点上,同时执行WorkerManager::slot2WorkerManager::process似乎很尴尬。但这完全没问题,至少如果WorkerManager是线程安全的。Worker完成后不久,WorkerManager::process完成,a.exec()被执行,但没有太多要处理的内容。

案例4

Main 函数只是启动 WorkerManager 的线程并立即转到 a.exec(),导致 end 作为输出的第一行。 a.exec()处理某些东西并确保程序执行,但不执行WorkerManager的插槽,因为它属于另一个线程。 WorkerManager::processWorkerManager 的事件循环中执行。 Worker 的线程启动,Worker::process开始从Worker的线程向WorkerManager的线程发送信号。不幸的是,后者正忙于执行WorkerManager::process。当Worker完成后,WorkerManager::process也会完成,WorkerManager 的线程会立即执行所有排队的插槽。

代码中最大的问题是usleep循环和无限循环。在使用Qt时,你几乎不应该使用它们。我知道Worker::process睡眠只是一些实际计算的占位符。但是您应该从WorkerManager中删除睡眠和无限循环。使用 WorkerManager::slot1 检测 Worker 的终止。如果您开发 GUI 应用程序,则无需将WorkerManager移动到另一个线程。它的所有方法(不休眠)都将快速执行,并且不会冻结 GUI。