Qt5: comment attendre un signal dans un thread?

probablement la question du titre n'est pas très explicite. J'utilise Qt5 sur Windows7.

dans un thread (QThread) à un certain point, dans le "process()" fonction / méthode, je dois attendre le "encrypted()" SIGNAL appartenant à un QSslSocket que j'utilise dans ce thread. Je suppose aussi que je devrais utiliser un QTimer et attendre un "timeout()" SIGNAL pour éviter d'être bloqué dans une boucle infinie...

Ce que j'ai maintenant est:

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

Eh bien, évidemment, ça ne marche pas. Il reste dans cette boucle pour toujours...

La question est donc: Est-il un moyen de résoudre ce problème? Comment puis-je attendre pour que "encrypted()" SIGNAL mais pas plus de-disons 10 secondes-afin d'éviter de rester coincé dans cette boucle/fil d'attente?

13
demandé sur סטנלי גרונן 2015-07-11 18:18:50

3 réponses

vous pouvez utiliser une boucle locale d'événement pour attendre que le signal soit émis:

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

ici il attend jusqu'à encrypted est émis ou le délai atteint.

27
répondu Nejat 2015-07-15 04:18:56

dans la programmation asynchrone, le" wait for " est considéré comme un anti-pattern. Au lieu d'attendre les choses, Concevez le code pour réagir à une condition qui se réalise. Par exemple, connectez le code à un signal.

une façon de mettre en œuvre ceci est de découper vos actions en états séparés, et de faire un peu de travail lorsque chacun des États est entré. Bien sûr, si la quantité de travail est non triviale, utilisez une fente séparée au lieu d'une lambda pour garder les choses lisibles.

Note l'absence de gestion explicite de la mémoire. L'utilisation de posséder des pointeurs vers les classes Qt est une optimisation prématurée et devrait être évitée lorsque cela n'est pas nécessaire. Les objets peuvent être des membres directs du Worker (ou PIMPL).

les sous-objets doivent tous faire partie de la hiérarchie de propriété qui a Worker à la racine. De cette façon, vous pouvez déménager en toute sécurité Worker instance à un autre thread, et les objets qu'il utilise le suivront. Bien sûr, vous pouvez également instancier le Worker dans le bon thread - il y a un simple langage pour que. Le répartiteur d'événements du thread possède le worker, donc lorsque la boucle d'événements du thread cesse (c.-à-d. après l'invocation QThread::quit()), le travailleur sera automatiquement éliminé et aucune ressource ne fuira.

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

la mise en œuvre du travailleur:

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

Puis:

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

notez que se connecter à QThread::started() serait racé: le répartiteur d'évènement n'existe pas jusqu'à ce code dans QThread::run() avait une chance d'exécuter. Nous devons donc attendre que le fil arrive à destination en cédant - ce qui est très susceptible d'amener le fil ouvrier à progresser assez loin dans un ou deux rendements. Ainsi, il ne perdra pas beaucoup de temps.

5
répondu Kuba Ober 2018-03-05 16:45:19

j'ai eu du temps ces jours-ci et j'ai fait une enquête...

Eh bien, j'ai visionné "http://doc.qt.io/qt-5/qsslsocket.html" et trouvé ceci:

bool QSslSocket::waitForEncrypted(int msecs = 30000)

a ma grande honte, Je ne l'avais pas remarqué avant... : (

Certainement besoin d'acheter des lunettes (malheureusement, c'est pas une blague!)

Je suis prêt à modifier mon code en conséquence afin de le tester (le lundi @ office).

Joli beaucoup de chances que ça marchera. Que voulez-vous dire: ne serait-il faire le travail?

Oui, un peu bizarre de répondre à ma propre question, mais peut-être que C'est une solution, alors j'ai décidé de partager :)

3
répondu סטנלי גרונן 2015-07-17 16:55:10