【问题标题】:Qt5: How to wait for a signal in a thread?Qt5:如何在线程中等待信号?
【发布时间】:2015-09-30 06:11:42
【问题描述】:

可能标题问题不是很明确。我在 Windows7 上使用 Qt5。

在某个线程 (QThread) 中,在 "process()" 函数/方法中,我必须等待属于我在该线程中使用的 QSslSocket 的 "encrypted()" SIGNAL。另外我想我应该使用 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循环中......
所以,问题是:有没有办法解决这个问题?我怎样才能等待 "encrypted()" SIGNAL 但不超过 - 比如说 10 秒 - 以避免陷入该等待循环/线程?

【问题讨论】:

    标签: c++ multithreading qt timer wait


    【解决方案1】:

    您可以使用本地事件循环来等待信号发出:

    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被发出或超时。

    【讨论】:

    • 好主意。我对其进行了测试(进行了一些修改以适合我的代码和目的),它工作正常。我当然投了赞成票:)
    • 简洁明了。太棒了。
    • 这样使用事件循环会导致“忙等待”,对吧?
    • @harihardik 否。在等待主线程中的所有事件都被处理并且 GUI 工作正常时。但是这种编码方式不好,建议使用异步方法。
    • @Pa_ 您可以简单地将encrypted 信号连接到带有参数的 lambda,并在 lambda 中调用事件循环的 quit 方法(应在 lambda 中捕获对事件循环对象的引用)。
    【解决方案2】:

    在异步编程中,“等待”被认为是一种反模式。与其等待事情发生,不如设计代码以对满足的条件做出反应。例如,将代码连接到信号。

    实现这一点的一种方法是将您的操作分割成单独的状态,并在进入每个状态时做一些工作。当然,如果工作量很大,请使用单独的插槽而不是 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);
      });
    }
    

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

    然后:

    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() 中的某些代码有机会执行之前,事件调度程序不存在。因此,我们必须通过让步等待线程到达那里——这很可能让工作线程在一两次让步内取得足够长的进展。因此不会浪费太多时间。

    【讨论】:

    • 非常有趣的方法!我明天会测试它(在调整它以适应我的代码之后),我会尝试提供相关的反馈:) 嗯,在线程中有一个状态机真是太好了!对不起,我很热情,但我是 Qt 的新手,所以每次我看到新的东西(对我来说)我都意识到 Qt/C++ 可以提​​供多么巨大的可能性......!我希望我能看到更多这样的例子!我能说什么?太棒了,好主意!
    • 您能否进一步扩展一下:“请注意没有显式内存管理。使用指向 Qt 类的拥有指针是一种过早的优化,在不必要的情况下应避免使用。”
    • @Mitch 手动内存管理是您 new 将其分配给原始 C 指针,然后必须手动删除它。该代码甚至没有明确的内存分配new,但如果它有它们,结果将立即分配给智能指针或在QObject 树中创建子级。请注意,QObject 充当其他 QObjects 的智能指针集合。
    • @Mitch 许多糟糕的 Qt 示例,包括那些与 Qt 捆绑的示例,使用完全不必要的显式堆分配。由于每个 重要的Qt 类都分配了一个PIMPL,因此通过将相当于PIMPL 指针的内容放在堆上,您将分配数量翻倍。 QObjectQWidget 的指针大小约为 4-6 个指针,即使它们的 PIMPL 大两个数量级。如果在构造函数中添加子元素,只需将它们作为常规(非指针)成员添加到类或其 PIMPL。
    • 谢谢。我不确定您是指基于QObject 的内存管理还是智能指针。
    【解决方案3】:

    这些天我有一些时间,我做了一些调查......
    好吧,我浏览了“http://doc.qt.io/qt-5/qsslsocket.html”发现了这个:

    bool QSslSocket::waitForEncrypted(int msecs = 30000)
    

    我真的很惭愧,我以前没有注意到它...... :(
    绝对需要买一些眼镜(很遗憾,这不是开玩笑!)
    我愿意相应地修改我的代码以便对其进行测试(周一@办公室)。
    很有可能它会起作用。
    [后期编辑]:
    是的,这是我在最终代码中实现的解决方案,效果很好,所以我决定分享:)

    【讨论】:

      【解决方案4】:

      从 Qt 5.0 开始,QSignalSpy 提供了一个等待方法。你把它连接到一个信号上,wait() 将阻塞直到信号触发。

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

      【讨论】:

        猜你喜欢
        • 2013-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多