Qt5:如何在线程中等待信号?
Qt5: How to wait for a signal in a thread?
可能标题问题不是很明确。我在 Windows7.
上使用 Qt5
在某个线程 (QThread) 中,在 "process()"
function/method 中,我必须等待 "encrypted()"
SIGNAL 属于我在该线程中使用的 QSslSocket。另外我想我应该使用 QTimer 并等待 "timeout()"
SIGNAL 以避免在无限循环中被阻塞...
我现在拥有的是:
// start processing data
void Worker::process()
{
status = 0;
connect(sslSocket, SIGNAL(encrypted()), this, SLOT(encryptionStarted()));
QTimer timer;
connect(&timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
timer.start(10000);
while(status == 0)
{
QThread::msleep(5);
}
qDebug("Ok, exited loop!");
// other_things here
// .................
// end other_things
emit finished();
}
// slot (for timer)
void Worker::timerTimeout()
{
status = 1;
}
// slot (for SSL socket encryption ready)
void Worker::encryptionStarted()
{
status = 2;
}
好吧,显然是行不通的。它永远留在 while-loop 中...
那么,问题来了:有没有办法解决这个问题?我怎样才能等待 "encrypted()"
SIGNAL 但不超过 - 比如说 10 秒 - 以避免陷入 waiting-loop/thread?
您可以使用本地事件循环来等待信号发出:
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect( sslSocket, &QSslSocket::encrypted, &loop, &QEventLoop::quit );
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
timer.start(msTimeout);
loop.exec();
if(timer.isActive())
qDebug("encrypted");
else
qDebug("timeout");
在这里它一直等到发出 encrypted
或超时。
在异步编程中,"wait for" 被认为是一种反模式。不要等待事情发生,而是设计代码以对条件满足做出反应。例如,将代码连接到信号。
实现这一点的一种方法是将您的操作分成单独的状态,并在进入每个状态时做一些工作。当然,如果工作量很大,请使用单独的槽而不是 lambda 来保持可读性。
请注意没有明确的内存管理。使用拥有指向 Qt 类 的指针是过早的优化,在不必要的地方应该避免。对象可以是 Worker
(或其 PIMPL)的直接成员。
子对象必须都是根部具有 Worker
的所有权层次结构的一部分。这样,您就可以安全地将 Worker
实例移动到另一个线程,并且它使用的对象将跟随它。当然,您也可以在正确的线程中实例化 Worker
- 有一个简单的 idiom。线程的事件调度器拥有worker,因此当线程的事件循环退出时(即调用QThread::quit()
后),worker将自动释放,不会泄漏资源。
template <typename Obj>
void instantiateInThread(QThread * thread) {
Q_ASSERT(thread);
QObject * dispatcher = thread->eventDispatcher();
Q_ASSERT(dispatcher); // the thread must have an event loop
QTimer::singleShot(0, dispatcher, [dispatcher](){
// this happens in the given thread
new Obj(dispatcher);
});
}
工人的实施:
class Worker : public QObject {
Q_OBJECT
QSslSocket sslSocket;
QTimer timer;
QStateMachine machine;
QState s1, s2, s3;
Q_SIGNAL void finished();
public:
explicit Worker(QObject * parent = {}) : QObject(parent),
sslSocket(this), timer(this), machine(this),
s1(&machine), s2(&machine), s3(&machine) {
timer.setSingleShot(true);
s1.addTransition(&sslSocket, SIGNAL(encrypted()), &s2);
s1.addTransition(&timer, SIGNAL(timeout()), &s3);
connect(&s1, &QState::entered, [this]{
// connect the socket here
...
timer.start(10000);
});
connect(&s2, &QState::entered, [this]{
// other_things here
...
// end other_things
emit finished();
});
machine.setInitialState(&s1);
machine.start();
}
};
然后:
void waitForEventDispatcher(QThread * thread) {
while (thread->isRunning() && !thread->eventDispatcher())
QThread::yieldCurrentThread();
}
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
struct _ : QThread { ~Thread() { quit(); wait(); } thread;
thread.start();
waitForEventDispatcher(&thread);
instantiateInThread<Worker>(&myThread);
...
return app.exec();
}
请注意,连接到 QThread::started()
会很有趣:在 QThread::run()
中的某些代码有机会执行之前,事件调度程序不存在。因此,我们必须等待线程通过让步到达那里——这很可能让工作线程在一两次让步内取得足够的进展。这样就不会浪费太多时间了。
这几天有空,做了一些调查...
好吧,我浏览了“http://doc.qt.io/qt-5/qsslsocket.html”,发现了这个:
bool QSslSocket::waitForEncrypted(int msecs = 30000)
真遗憾,我之前没有注意到它...:(
一定要买副眼镜(可惜,不是开玩笑的!)
我愿意相应地修改我的代码以便对其进行测试(周一@办公室)。
很有可能它会起作用。
[后期编辑]:
是的,这是我在最终代码中实现的解决方案并且效果很好,所以我决定分享 :)
从 Qt 5.0 开始,QSignalSpy 提供了一个等待方法。你将它连接到一个信号,wait() 将阻塞直到信号触发。
QSignalSpy spy(SIGNAL(encrypted()));
spy.wait(5000); //wait until signal fires or 5 second timeout expires
可能标题问题不是很明确。我在 Windows7.
上使用 Qt5在某个线程 (QThread) 中,在 "process()"
function/method 中,我必须等待 "encrypted()"
SIGNAL 属于我在该线程中使用的 QSslSocket。另外我想我应该使用 QTimer 并等待 "timeout()"
SIGNAL 以避免在无限循环中被阻塞...
我现在拥有的是:
// start processing data
void Worker::process()
{
status = 0;
connect(sslSocket, SIGNAL(encrypted()), this, SLOT(encryptionStarted()));
QTimer timer;
connect(&timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
timer.start(10000);
while(status == 0)
{
QThread::msleep(5);
}
qDebug("Ok, exited loop!");
// other_things here
// .................
// end other_things
emit finished();
}
// slot (for timer)
void Worker::timerTimeout()
{
status = 1;
}
// slot (for SSL socket encryption ready)
void Worker::encryptionStarted()
{
status = 2;
}
好吧,显然是行不通的。它永远留在 while-loop 中...
那么,问题来了:有没有办法解决这个问题?我怎样才能等待 "encrypted()"
SIGNAL 但不超过 - 比如说 10 秒 - 以避免陷入 waiting-loop/thread?
您可以使用本地事件循环来等待信号发出:
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect( sslSocket, &QSslSocket::encrypted, &loop, &QEventLoop::quit );
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
timer.start(msTimeout);
loop.exec();
if(timer.isActive())
qDebug("encrypted");
else
qDebug("timeout");
在这里它一直等到发出 encrypted
或超时。
在异步编程中,"wait for" 被认为是一种反模式。不要等待事情发生,而是设计代码以对条件满足做出反应。例如,将代码连接到信号。
实现这一点的一种方法是将您的操作分成单独的状态,并在进入每个状态时做一些工作。当然,如果工作量很大,请使用单独的槽而不是 lambda 来保持可读性。
请注意没有明确的内存管理。使用拥有指向 Qt 类 的指针是过早的优化,在不必要的地方应该避免。对象可以是 Worker
(或其 PIMPL)的直接成员。
子对象必须都是根部具有 Worker
的所有权层次结构的一部分。这样,您就可以安全地将 Worker
实例移动到另一个线程,并且它使用的对象将跟随它。当然,您也可以在正确的线程中实例化 Worker
- 有一个简单的 idiom。线程的事件调度器拥有worker,因此当线程的事件循环退出时(即调用QThread::quit()
后),worker将自动释放,不会泄漏资源。
template <typename Obj>
void instantiateInThread(QThread * thread) {
Q_ASSERT(thread);
QObject * dispatcher = thread->eventDispatcher();
Q_ASSERT(dispatcher); // the thread must have an event loop
QTimer::singleShot(0, dispatcher, [dispatcher](){
// this happens in the given thread
new Obj(dispatcher);
});
}
工人的实施:
class Worker : public QObject {
Q_OBJECT
QSslSocket sslSocket;
QTimer timer;
QStateMachine machine;
QState s1, s2, s3;
Q_SIGNAL void finished();
public:
explicit Worker(QObject * parent = {}) : QObject(parent),
sslSocket(this), timer(this), machine(this),
s1(&machine), s2(&machine), s3(&machine) {
timer.setSingleShot(true);
s1.addTransition(&sslSocket, SIGNAL(encrypted()), &s2);
s1.addTransition(&timer, SIGNAL(timeout()), &s3);
connect(&s1, &QState::entered, [this]{
// connect the socket here
...
timer.start(10000);
});
connect(&s2, &QState::entered, [this]{
// other_things here
...
// end other_things
emit finished();
});
machine.setInitialState(&s1);
machine.start();
}
};
然后:
void waitForEventDispatcher(QThread * thread) {
while (thread->isRunning() && !thread->eventDispatcher())
QThread::yieldCurrentThread();
}
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
struct _ : QThread { ~Thread() { quit(); wait(); } thread;
thread.start();
waitForEventDispatcher(&thread);
instantiateInThread<Worker>(&myThread);
...
return app.exec();
}
请注意,连接到 QThread::started()
会很有趣:在 QThread::run()
中的某些代码有机会执行之前,事件调度程序不存在。因此,我们必须等待线程通过让步到达那里——这很可能让工作线程在一两次让步内取得足够的进展。这样就不会浪费太多时间了。
这几天有空,做了一些调查...
好吧,我浏览了“http://doc.qt.io/qt-5/qsslsocket.html”,发现了这个:
bool QSslSocket::waitForEncrypted(int msecs = 30000)
真遗憾,我之前没有注意到它...:(
一定要买副眼镜(可惜,不是开玩笑的!)
我愿意相应地修改我的代码以便对其进行测试(周一@办公室)。
很有可能它会起作用。
[后期编辑]:
是的,这是我在最终代码中实现的解决方案并且效果很好,所以我决定分享 :)
从 Qt 5.0 开始,QSignalSpy 提供了一个等待方法。你将它连接到一个信号,wait() 将阻塞直到信号触发。
QSignalSpy spy(SIGNAL(encrypted()));
spy.wait(5000); //wait until signal fires or 5 second timeout expires