【问题标题】:Event loops and signal-slot processing when using multithreading in Qt在 Qt 中使用多线程时的事件循环和信号槽处理
【发布时间】:2015-03-19 07:05:28
【问题描述】:

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

我在 OUTPUT 部分添加了一些 cmets,但正如您所见,我不确定我对导致观察到的行为的假设是否正确。另外我不确定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();
}

输出 - slot1slot2 都在 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-&gt;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 移动到单独的线程,线程已启动,但使用来自 threadstarted() 信号调用了 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);
    QObject::connect(thread,SIGNAL(started()),workerManager,SLOT(process()));
    thread->start();
    qDebug() << "end";
    return a.exec();
}

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

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

感谢您的任何澄清。

【问题讨论】:

    标签: c++ multithreading qt event-loop signals-slots


    【解决方案1】:

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

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

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

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

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

    案例一

    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。

    【讨论】:

      猜你喜欢
      • 2018-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-08
      • 2013-07-20
      • 1970-01-01
      相关资源
      最近更新 更多