在 Qt 中使用多线程时的事件循环和信号槽处理
Event loops and signal-slot processing when using multithreading in 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 (case 1 - no separate thread for 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();
}
OUTPUT - both slot1
and slot2
called at a.exec()
(??? - using main event loop?):
starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
slot2
slot2
slot2
slot2
slot2
slot1
main.cpp (case 2 - workerManager
moved to separate thread, but thread not 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);
workerManager->process();
qDebug() << "end";
return a.exec();
}
OUTPUT - neither slot1
nor slot2
was called - (??? event loop associated with thread receives signals but since thread was not started slots are not called?):
starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
main.cpp (case 3 - workerManager
moved to separate thread, thread started but workerManager::process()
called via 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();
}
OUTPUT - slot2
called while Worker
still executing its process()
(???):
starting
Worker(0x197bb20) processing started
slot2
slot2
slot2
slot2
Worker(0x197bb20) processing finished
finished
end
slot2
slot1
main.cpp (case 4 - workerManager
moved to separate thread, thread started but workerManager::process()
called using started()
signal from 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();
}
OUTPUT - all events processed after reaching 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()
或其他一些方法给出)。一旦事件处理程序完成,控制流 returns 到事件循环,Qt 可能会执行另一个处理程序来处理另一个事件。
信号和槽与 Qt 术语中的事件和事件处理程序不同。但是槽是由事件循环处理的,有点类似。如果您的代码中有控制流(例如在 main
函数中),您可以像任何其他 C++ 函数一样立即执行任何插槽。但是当 Qt 这样做时,它只能从事件循环中做到这一点。应该注意的是,信号总是立即发送,而插槽执行可能会延迟。
现在让我们看看每种情况下会发生什么。
案例一
WorkerManager::process
在程序开始时直接执行。新线程启动,Worker::process
立即在新线程中执行。 WorkerManager::process
继续执行直到 Worker 完成,冻结主线程中的所有其他操作(包括槽处理)。 WorkerManager::process
完成后,控制流转到 QApplication::exec
。 Qt 建立与另一个线程的连接,接收有关槽调用的消息并随后调用所有这些消息。
案例二
Qt 默认在对象所属的线程中执行对象的槽。主线程不会执行 WorkerManager
的槽,因为它属于另一个线程。然而,这个线程从未启动过。它的事件循环永远不会结束。 slot1
和 slot2
的调用将永远留在 Qt 的队列中,等待您启动线程。悲伤的故事。
案例三
在这种情况下 WorkerManager::process
在主线程中执行,因为您直接从主线程调用它。同时,WorkerManager
的线程启动。它的事件循环启动并等待事件。 WorkerManager::process
启动 Worker
的线程并在其中执行 Worker::exec
。 Worker
开始向 WorkerManager
发送信号。 WorkerManager
的线程几乎立即开始执行适当的插槽。此时 WorkerManager::slot2
和 WorkerManager::process
同时执行似乎很尴尬。但它非常好,至少如果 WorkerManager
是线程安全的。 Worker
完成后不久,WorkerManager::process
完成并执行 a.exec()
但没有太多要处理的。
案例 4
Main 函数刚刚启动 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。
我在使用 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 (case 1 - no separate thread for
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();
}
OUTPUT - both
slot1
andslot2
called ata.exec()
(??? - using main event loop?):
starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
slot2
slot2
slot2
slot2
slot2
slot1
main.cpp (case 2 -
workerManager
moved to separate thread, but thread not 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);
workerManager->process();
qDebug() << "end";
return a.exec();
}
OUTPUT - neither
slot1
norslot2
was called - (??? event loop associated with thread receives signals but since thread was not started slots are not called?):
starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
main.cpp (case 3 -
workerManager
moved to separate thread, thread started butworkerManager::process()
called viaworkerManager->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();
}
OUTPUT -
slot2
called whileWorker
still executing itsprocess()
(???):
starting
Worker(0x197bb20) processing started
slot2
slot2
slot2
slot2
Worker(0x197bb20) processing finished
finished
end
slot2
slot1
main.cpp (case 4 -
workerManager
moved to separate thread, thread started butworkerManager::process()
called usingstarted()
signal fromthread
):
#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 - all events processed after reaching
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()
或其他一些方法给出)。一旦事件处理程序完成,控制流 returns 到事件循环,Qt 可能会执行另一个处理程序来处理另一个事件。
信号和槽与 Qt 术语中的事件和事件处理程序不同。但是槽是由事件循环处理的,有点类似。如果您的代码中有控制流(例如在 main
函数中),您可以像任何其他 C++ 函数一样立即执行任何插槽。但是当 Qt 这样做时,它只能从事件循环中做到这一点。应该注意的是,信号总是立即发送,而插槽执行可能会延迟。
现在让我们看看每种情况下会发生什么。
案例一
WorkerManager::process
在程序开始时直接执行。新线程启动,Worker::process
立即在新线程中执行。 WorkerManager::process
继续执行直到 Worker 完成,冻结主线程中的所有其他操作(包括槽处理)。 WorkerManager::process
完成后,控制流转到 QApplication::exec
。 Qt 建立与另一个线程的连接,接收有关槽调用的消息并随后调用所有这些消息。
案例二
Qt 默认在对象所属的线程中执行对象的槽。主线程不会执行 WorkerManager
的槽,因为它属于另一个线程。然而,这个线程从未启动过。它的事件循环永远不会结束。 slot1
和 slot2
的调用将永远留在 Qt 的队列中,等待您启动线程。悲伤的故事。
案例三
在这种情况下 WorkerManager::process
在主线程中执行,因为您直接从主线程调用它。同时,WorkerManager
的线程启动。它的事件循环启动并等待事件。 WorkerManager::process
启动 Worker
的线程并在其中执行 Worker::exec
。 Worker
开始向 WorkerManager
发送信号。 WorkerManager
的线程几乎立即开始执行适当的插槽。此时 WorkerManager::slot2
和 WorkerManager::process
同时执行似乎很尴尬。但它非常好,至少如果 WorkerManager
是线程安全的。 Worker
完成后不久,WorkerManager::process
完成并执行 a.exec()
但没有太多要处理的。
案例 4
Main 函数刚刚启动 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。