简体   繁体   English

Qt5:如何在线程中等待信号?

[英]Qt5: How to wait for a signal in a thread?

Probably the title question is not very explicit.可能标题问题不是很明确。 I am using Qt5 on Windows7.我在 Windows7 上使用 Qt5。

In a thread (QThread) at some point, in the "process()" function/method, I must wait for the "encrypted()" SIGNAL belonging to a QSslSocket I am using in this thread.在某个线程 (QThread) 中,在"process()"函数/方法中,我必须等待属于我在该线程中使用的 QSslSocket 的"encrypted()"信号。 Also I suppose I should use a QTimer and wait for a "timeout()" SIGNAL in order to avoid getting blocked in an infinite loop...另外我想我应该使用 QTimer 并等待"timeout()"信号以避免在无限循环中被阻塞......
What I have now is:我现在拥有的是:

// 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;
}

Well, obviously it doesn't work.好吧,显然它不起作用。 It stays in that while-loop forever...它永远停留在那个while循环中......
So, the question is: Is there a way to solve this problem?所以,问题是:有没有办法解决这个问题? How can I wait for that "encrypted()" SIGNAL but not more than - let's say 10 seconds - in order to avoid getting stuck in that waiting-loop/thread?我怎样才能等待"encrypted()"信号但不超过 - 假设 10 秒 - 以避免陷入该等待循环/线程?

You can use a local event loop to wait for the signal to be emitted :您可以使用本地事件循环来等待发出信号:

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");

Here it waits until encrypted is emitted or the the timeout reaches.在这里它等待直到encrypted被发出或超时到达。

In asynchronous programming, the "wait for" is considered an anti-pattern.在异步编程中,“等待”被认为是一种反模式。 Instead of waiting for things, design the code to react to a condition becoming fulfilled.与其等待事情发生,不如设计代码以对满足的条件做出反应。 Eg, connect the code to a signal.例如,将代码连接到信号。

One way of implementing this is to slice your actions into separate states, and do some work when each of the states is entered.实现这一点的一种方法是将您的操作分成不同的状态,并在进入每个状态时做一些工作。 Of course if the amount of work is non-trivial, use a separate slot instead of a lambda to keep things readable.当然,如果工作量很大,请使用单独的插槽而不是 lambda 来保持可读性。

Note the absence of explicit memory management.注意没有显式的内存管理。 Use of owning pointers to Qt classes is a premature optimization and should be avoided where unnecessary.使用拥有指向 Qt 类的指针是一种过早的优化,应该避免在不必要的情况下。 The objects can be direct members of the Worker (or its PIMPL ).对象可以是Worker (或其PIMPL )的直接成员。

The sub-objects must be all a part of the ownership hierarchy that has Worker at the root.子对象必须都是以Worker为根的所有权层次结构的一部分。 That way, you can safely move the Worker instance to another thread, and the objects it uses will follow it.这样,您可以安全地将Worker实例移动到另一个线程,并且它使用的对象将跟随它。 Of course you could also instantiate the Worker in the correct thread - there's a simple idiom for that.当然,您也可以在正确的线程中实例化Worker - 对此有一个简单的习惯用法 The thread's event dispatcher owns the worker, thus when the thread's event loop quits (ie after invoking QThread::quit() ), the worker will be automatically disposed and no resources will leak.线程的事件调度器拥有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);
  });
}

The Worker's implementation: Worker 的实现:

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();
  }
};

Then:然后:

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();
}

Note that connecting to QThread::started() would be racy: the event dispatcher doesn't exist until some code within QThread::run() had a chance to execute.请注意,连接到QThread::started()会很有趣:在QThread::run()某些代码有机会执行之前,事件调度程序不存在。 Thus we have to wait for the thread to get there by yielding - this is very likely to get the worker thread to progress far enough within one or two yields.因此,我们必须等待线程通过让步到达那里 - 这很可能使工作线程在一两个产量内取得足够的进展。 Thus it won't waste much time.因此不会浪费太多时间。

Starting in Qt 5.0, QSignalSpy offers a wait method.从 Qt 5.0 开始, QSignalSpy提供了一个等待方法。 You hook it up to a signal and wait() will block until the signal fires.你把它连接到一个信号上,wait() 将阻塞直到信号触发。

QSignalSpy spy(SIGNAL(encrypted()));
spy.wait(5000);  //wait until signal fires or 5 second timeout expires

I had some time these days and I did some investigation...这些天我有一些时间,我做了一些调查......
Well, I browsed "http://doc.qt.io/qt-5/qsslsocket.html" and found this:好吧,我浏览了“http://doc.qt.io/qt-5/qsslsocket.html”,发现了这个:

bool QSslSocket::waitForEncrypted(int msecs = 30000)

To my real shame, I didn't noticed it before... :(令我真正感到羞耻的是,我之前没有注意到它...... :(
Definitely need to buy some glasses ( unfortunately , it's not a joke!)肯定需要买一些眼镜(不幸的是,这不是开玩笑!)
I am willing to modify my code accordingly in order to test it (on Monday @ office).我愿意相应地修改我的代码以对其进行测试(星期一@办公室)。
Pretty much chances that it'll work.很有可能它会起作用。
[ Late edit ]: [后期编辑]:
Yes, this is the solution I implemented in my final code and it works well, so I decided to share :)是的,这是我在最终代码中实现的解决方案,效果很好,所以我决定分享:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM