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


我在使用时遇到了一些问题 QThreads 这让我在找到合适的组合之前探索了不同的组合。但是,在事件循环和信号槽处理方面,我仍然不完全理解下面所示的四种情况。

我在OUTPUT部分添加了一些注释,但正如您所看到的,我不确定我对观察到的行为的原因是否正确。我也不确定是否 case 3 是可以在实际代码中使用的东西。这是我的测试代码(仅限于 main.cpp 每个案例都有所不同):

worker.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_;
};

workermanager.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();
}

输出 - 两者 slot1 和 slot2 叫来 a.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();
}

输出 - slot2 叫来 Worker 仍在执行它 process() (???):

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

main.cpp(案例4 - workerManager 移动到单独的线程,线程启动但是 workerManager::process() 叫做使用 started() 信号来自 thread):

#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 

谢谢你的任何澄清。


3383
2018-01-20 21:48


起源



答案:


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

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

当Qt决定处理事件时,它会执行事件处理程序。当事件处理程序正在运行时,Qt没有机会处理任何其他事件(除非直接由 QApplication::processEvents() 或其他一些方法)。一旦事件处理程序完成,控制流返回到事件循环,Qt可以执行另一个处理程序来处理另一个事件。

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

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

情况1

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

案例2

Qt默认执行该对象所属线程中对象的槽。主线程不会执行插槽 WorkerManager 因为它属于另一个线程。但是这个线程永远不会启动。它的事件循环永远不会完成。 Invokations of slot1 和 slot2 永远留在Qt的队列中,等待你启动线程。悲剧。

案例3

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

案例4

主要功能刚刚启动 WorkerManager的线程,立即去 a.exec(), 导致 end 作为输出的第一行。 a.exec() 处理某些事情并确保程序执行但不执行 WorkerManager的插槽,因为它属于另一个线程。 WorkerManager::process 被执行 WorkerManager来自事件循环的线程。 Worker的线程已经开始了 Worker::process 开始发送信号 Worker的主题是 WorkerManager的线索。不幸的是后者正在忙着执行 WorkerManager::process。什么时候 Worker 已经完成了, WorkerManager::process 也完成了 WorkerManager的线程立即执行所有排队的插槽。

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


16
2018-01-20 23:14