Подтвердить что ты не робот

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

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

В потоке (QThread) в какой-то момент, в функции/методе "process()", я должен дождаться символа "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 forever...
Итак, вопрос: есть ли способ решить эту проблему? Как я могу ждать этого "encrypted()" SIGNAL, но не более - пусть говорят 10 секунд - чтобы избежать застревания в этом цикле ожидания/потока?

4b9b3361

Ответ 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 будет отправлено или истечет время ожидания.

Ответ 2

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

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

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

Все подобъекты должны быть частью иерархии владения, в которой Worker в корне. Таким образом, вы можете безопасно переместить экземпляр Worker в другой поток, и объекты, которые он использует, будут следовать за ним. Конечно, вы также можете создать экземпляр Worker в правильном потоке - для этого есть простая идиома. Диспетчер событий потока владеет рабочим, поэтому при QThread::quit() цикла событий потока (т. QThread::quit() После вызова 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() не QThread::run() выполнить. Таким образом, мы должны ждать, пока поток достигнет этого, уступив - очень вероятно, что рабочий поток продвинется достаточно далеко в течение одного или двух выходов. Таким образом, это не будет тратить много времени.

Ответ 3

У меня было некоторое время в эти дни, и я провел некоторое расследование...
Ну, я просмотрел " http://doc.qt.io/qt-5/qsslsocket.html" и нашел это:

bool QSslSocket::waitForEncrypted(int msecs = 30000)

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

Ответ 4

Доброе утро всем, дорогой Куба Обер. Я просто не понимаю следующие две строки в вашем примере программы: waitForEventDispatcher (& thread); instantiateInThread (& MyThread);

Почему имена переменных не идентичны? (нить, myThread) Это не та же самая тема, на которую вы ссылаетесь? Если нет, я не понимаю, не могли бы вы объяснить больше? Еще один, я не вижу никакого объявления переменной myThread.

Спасибо за ваш будущий ответ