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

Moo*_*min 11 c++ qt multithreading event-loop signals-slots

我在使用时遇到了一些问题,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_;
};
Run Code Online (Sandbox Code Playgroud)

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"; }
};
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

输出-都slot1slot2在所谓的a.exec()(? -使用主事件循环?):

starting 
Worker(0x112db20) processing started 
Worker(0x112db20) processing finished 
finished 
end
slot2 
slot2 
slot2 
slot2 
slot2 
slot1 
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

OUTPUT - 既不是slot1也没有slot2被调用 - (???与线程相关的事件循环接收信号,但由于线程未启动,因此不会调用槽?):

starting 
Worker(0x112db20) processing started 
Worker(0x112db20) processing finished 
finished 
end
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

OUTPUT - slot2Worker执行其process()(???)时调用:

starting 
Worker(0x197bb20) processing started 
slot2 
slot2 
slot2 
slot2 
Worker(0x197bb20) processing finished 
finished 
end 
slot2 
slot1 
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

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

end 
starting 
Worker(0x7f1d700013d0) processing started 
Worker(0x7f1d700013d0) processing finished 
finished 
slot2 
slot2 
slot2 
slot2 
slot2 
slot1 
Run Code Online (Sandbox Code Playgroud)

谢谢你的任何澄清.

Pav*_*hov 17

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

事件循环是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因为它属于另一个线程.但是这个线程永远不会启动.它的事件循环永远不会完成.在Qt的队列中永远保留Invokations,slot1slot2等待您启动该线程.悲剧.

案例3

在这种情况下WorkerManager::process,在主线程中执行,因为您直接从主线程调用它.与此同时,WorkerManager线程开始了.它的事件循环启动并等待事件.WorkerManager::process启动Worker的线程并Worker::exec在其中执行.Worker开始发送信号WorkerManager.WorkerManager的线程几乎立即开始执行适当的插槽.在这一点上,它似乎很尴尬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.