2015-07-11 8 views
12

Wahrscheinlich ist die Titelfrage nicht sehr explizit. Ich verwende Qt5 unter Windows7.Qt5: Wie man auf ein Signal in einem Thread wartet?

In einem Thread (QThread) an einem bestimmten Punkt, in der "process()" Funktion/Methode, muss ich auf das "encrypted()" SIGNAL eines QSslSocket, das ich in diesem Thread verwende, warten. Auch ich nehme an, ich soll einen QTimer verwenden und für ein "timeout()" SIGNAL, um in einer Endlosschleife zu vermeiden warten blockiert zu werden ...
Was ich habe, ist jetzt:

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

Nun, offensichtlich funktioniert es nicht . Es bleibt in der while-Schleife für immer ...
Also, die Frage ist: Gibt es eine Möglichkeit, dieses Problem zu lösen? Wie kann ich darauf warten, dass "encrypted()" SIGNAL aber nicht mehr als - sagen wir 10 Sekunden - um nicht in dieser Warteschleife/Thread stecken bleiben?

Antwort

21

Sie können eine lokale Ereignisschleife verwenden, um das Signal zu warten, um emittiert werden:

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

Hier wartet, bis encrypted emittiert wird, oder die das Timeout erreicht.

+0

Sehr gute Idee. Ich habe es getestet (mit einigen Änderungen, die meinem Code und meinem Zweck entsprechen) und es funktioniert in Ordnung. Ich habe natürlich gewählt :) –

4

In der asynchronen Programmierung wird das "Warten auf" als Anti-Pattern betrachtet. Anstatt auf Dinge zu warten, sollten Sie den Code so gestalten, dass er darauf reagiert, dass eine Bedingung erfüllt wird. Z. B. verbinden Sie den Code mit einem Signal.

Eine Möglichkeit, dies zu implementieren, besteht darin, Ihre Aktionen in separate Status zu zerlegen und bei der Eingabe jedes Status einige Arbeit zu leisten. Natürlich, wenn die Menge der Arbeit nicht trivial ist, verwenden Sie einen separaten Steckplatz anstelle eines Lambda, um die Dinge lesbar zu halten.

Beachten Sie das Fehlen einer expliziten Speicherverwaltung. Die Verwendung von besitzenden Zeigern zu Qt-Klassen ist eine vorzeitige Optimierung und sollte vermieden werden, wo dies unnötig ist. Die Objekte können direkte Mitglieder des Worker (oder seines PIMPL) sein.

Die Unterobjekte müssen alle Teil der Eigentümerhierarchie sein, die Worker im Stammverzeichnis hat. Auf diese Weise können Sie die Worker Instanz sicher in einen anderen Thread verschieben, und die Objekte, die sie verwendet, folgen ihr. Natürlich können Sie auch die Worker in den richtigen Thread instanziieren - es gibt eine einfache idiom dafür. Der Ereignis-Dispatcher des Threads besitzt den Worker. Wenn die Ereignisschleife des Threads beendet wird (d. H. Nach dem Aufruf von QThread::quit()), wird der Worker automatisch entfernt, und es werden keine Ressourcen verloren gehen.

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

Die Arbeiter Umsetzung:

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

Dann:

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

Beachten Sie, dass wäre rassig zu QThread::started() verbindet: der Event-Dispatcher existiert nicht erst einige Code innerhalb QThread::run() hatte eine Chance zu erfüllen. Daher müssen wir darauf warten, dass der Thread dorthin gelangt, indem er nachgibt. Dies führt sehr wahrscheinlich dazu, dass der Worker-Thread innerhalb einer oder zwei Renditen weit genug fortschreitet. So wird es nicht viel Zeit verschwenden.

+0

Sehr interessanter Ansatz! Ich werde es morgen testen (nachdem ich es an meinen Code angepasst habe) und ich werde versuchen, das entsprechende Feedback zu geben :) Hmmm, einfach toll, eine Statusmaschine in einem Thread zu haben! Entschuldigung, ich bin so enthusiastisch, aber ich bin Anfänger in Qt, also erkenne ich jedes Mal, wenn ich etwas Neues sehe (für mich), wie große Möglichkeiten Qt/C++ bieten kann ...! Ich wünschte, ich könnte mehr Beispiele sehen! Was kann ich sagen? Bravo, ausgezeichnete Idee! –

+0

Kannst du noch etwas weiter ausführen: "Beachte das Fehlen einer expliziten Speicherverwaltung. Die Verwendung von besitzenden Zeigern zu Qt-Klassen ist eine vorzeitige Optimierung und sollte vermieden werden, wo es nicht notwendig ist." – Mitch

+1

@ Mitch Manuelle Speicherverwaltung ist, wo Sie etwas "neu" und es einem rohen C-Zeiger zuweisen, und müssen es dann manuell löschen. Der Code hat nicht einmal eine explizite Speicherzuweisung mit "neu", aber wenn er sie hätte, würden die Ergebnisse sofort entweder intelligenten Zeigern zugewiesen oder in einer "QObject" -Baumstruktur untergeordnet werden. Beachten Sie, dass ein 'QObject' als eine intelligente Pointer-Sammlung für andere' QObject's fungiert. –

3

Ich hatte einige Zeit in diesen Tagen und ich habe einige Untersuchungen ...
Nun, geblättert I "http://doc.qt.io/qt-5/qsslsocket.html" und fanden diese:

bool QSslSocket::waitForEncrypted(int msecs = 30000) 

Zu meiner wirklich schade, dass ich es vorher nicht bemerkt ... :(
Definitiv einige Gläser kaufen müssen (leider, Es ist kein Witz!)
Ich bin bereit, meinen Code entsprechend zu ändern, um es zu testen (am Montag @ Büro).
Ziemlich viel Chancen, dass es funktioniert. Was sagst du: würde es den Job tun?
Ja, irgendwie komisch, meine eigene Frage zu beantworten, aber vielleicht ist es eine Lösung, s o Ich entschied mich zu teilen :)