在Qt中使用多线程时的事件循环和信号槽处理
Event loops and signal-slot processing when using multithreading in Qt
我在使用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();
}
输出 -
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();
}
输出 -
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
插槽,因为它属于另一个线程。但是,此线程永远不会启动。它的事件循环永远不会完成。slot1
和slot2
的调用永远留在Qt的队列中,等待你启动线程。悲伤的故事。
案例3
在这种情况下,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。
- 具有Qt事件循环的可移植通用共享库设置
- 如何将事件循环中的事件分派给订阅者?
- C++uWebSockets将事件循环集成到一个线程中
- 了解如何在不冻结事件循环的情况下在 QThread 中休眠/等待
- 如何在主事件循环之前创建一些对象?
- Libuv:保护事件循环免受并发访问
- 防止模式对话框中的事件循环阻塞
- QCoreApplication事件循环和Windows服务控制处理程序功能
- c++ 过期映射条目线程与事件循环
- Qt的事件循环线程是安全的还是原子的?处理"队列连接"时如何同步?
- 在事件循环启动后删除并创建新的 RTP 流
- Qt在游戏循环中短时间内停止关键事件
- QSTATEMACHINE事件循环与动画
- 等待事件循环为空 /等到QT5小部件关闭
- 如何将lambda函数排队到Qt的事件循环中?
- DLL中的QT事件循环
- 运行Qt 5.10事件循环howto的Windows服务service_WIN32_OWN_PROCESS
- 如何在 Windows 上的C++控制台应用程序中捕获 ctrl-c 事件,而不会进入无限循环
- 模态qprogressdialog :: setValue()导致嵌套事件循环崩溃
- 什么是QT中的事件循环