Qt5:как ждать сигнала в потоке?

вероятно, вопрос заголовка не очень ясен. Я использую Qt5 на Windows7.

в потоке (QThread) в какой-то момент, в "process()" функция/метод, я должен ждать "encrypted()" сигнал, принадлежащий QSslSocket, который я использую в этом потоке. Также я полагаю, что я должен использовать QTimer и ждать "timeout()" сигнал, чтобы избежать блокировки в бесконечном цикле...
Теперь у меня есть:

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

Ну, очевидно, это не сработает. Он остается в это время-петля навсегда...
Итак, вопрос: есть ли способ решить эту проблему? Как я могу ждать этого "encrypted()" сигнал, но не более-скажем, 10 секунд-чтобы избежать застрять в этом цикле ожидания/потоке?

3 ответов


вы можете использовать локальный цикл событий, чтобы дождаться сигнала:

QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect(sslSocket,  SIGNAL(encrypted()), &loop, SLOT(quit()) );
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
timer.start(msTimeout);
loop.exec();

if(timer.isActive())
    qDebug("encrypted");
else
    qDebug("timeout");

здесь он ждет, пока encrypted испускается или тайм-аут достигает.


в асинхронном программировании "ожидание" считается анти-шаблоном. Вместо того, чтобы ждать чего-то, создайте код, чтобы реагировать на выполнение условия. Е. Г., подключите код в сигнал.

один из способов реализации этого-разбить ваши действия на отдельные состояния и выполнить некоторую работу, когда каждое из состояний введено. Конечно, если объем работы нетривиален, используйте отдельный слот вместо лямбды, чтобы держать вещи читаемыми.

Примечание. отсутствие явного управления памятью. Использование собственных указателей на классы Qt является преждевременной оптимизацией и следует избегать там, где это не нужно. Объекты могут быть прямыми членами Worker (или интеллекту).

подобъекты должны быть частью иерархии собственности, которая имеет Worker в корне. Таким образом, вы можете безопасно перемещать Worker экземпляр в другой поток, и объекты, которые он использует, будут следовать за ним. Конечно, вы также можете создать экземпляр Worker в правильном потоке-есть простой идиоматическое выражение для этого. Диспетчер событий потока владеет работником, поэтому, когда цикл событий потока завершается (т. е. после вызова QThread::quit()), работник будет автоматически размещан и никакие ресурсы не протекут.

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)

к моему настоящему стыду, я не заметил этого раньше... :(
Определенно нужно купить некоторые очки (к сожалению, это не шутка!)
Я готов соответствующим образом изменить свой код, чтобы протестировать его (в понедельник @ office).
Скорее всего, это сработает. Что скажешь?: сработает ли это?
Да, немного странно отвечать на мой собственный вопрос, но, возможно, это решение, поэтому я решил поделиться:)