2015-07-11 36 views
12

Prawdopodobnie tytułowe pytanie nie jest bardzo jednoznaczne. Używam Qt5 na Windows7.Qt5: Jak czekać na sygnał w wątku?

w wątku (QThread) w pewnym miejscu w "process()" funkcji/metody muszę czekać na "encrypted()" SIGNAL należącego do QSslSocket mi z wykorzystaniem w tym temacie. Przypuszczam też powinienem użyć QTimer i czekać na "timeout()" SIGNAL aby uniknąć zablokowania w nieskończonej pętli ...
Co mam teraz jest:

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

No, oczywiście nie działa . Pozostaje w tej pętli na zawsze ...
Pytanie brzmi: Czy istnieje sposób na rozwiązanie tego problemu? Jak mogę poczekać na tę SYNCHRONĘ "encrypted()", ale nie więcej niż - powiedzmy 10 sekund - aby uniknąć utknięcia w tej pętli oczekiwania/wątku?

Odpowiedz

21

Można użyć pętli lokalnej zdarzeń czekać na sygnał być emitowane:

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

Tutaj czeka aż encrypted jest emitowany lub timeout osiągnie.

+0

Bardzo dobry pomysł. Przetestowałem to (z pewnymi modyfikacjami pasującymi do mojego kodu i celu) i działa dobrze. Głosowałem oczywiście oczywiście :) –

4

W programowaniu asynchronicznym "oczekiwanie na" jest uważane za wzorzec przeciwny. Zamiast czekać na rzeczy, zaprojektuj kod, aby zareagował na spełnienie warunku. Np. Połącz kod z sygnałem.

Jednym ze sposobów realizacji tego jest podzielenie twoich działań na osobne stany i wykonanie pewnych czynności po wprowadzeniu każdego z nich. Oczywiście, jeśli ilość pracy nie jest banalna, użyj oddzielnego gniazda zamiast lambda, aby zachować czytelność.

Uwaga na brak jawnego zarządzania pamięcią. Używanie własnych wskaźników do klas Qt jest przedwczesną optymalizacją i należy tego unikać, gdy jest to niepotrzebne. Obiekty mogą być bezpośrednimi członkami Worker (lub ich PIMPL).

Pod-obiekty muszą być częścią hierarchii własności, która ma numer Worker w katalogu głównym. W ten sposób możesz bezpiecznie przenieść instancję Worker do innego wątku, a obiekty, których używa, będą podążać za nią. Oczywiście można również utworzyć instancję Worker we właściwym wątku - do tego jest prosty idiom. Program rozsyłający zdarzenia wątku jest właścicielem procesu roboczego, a zatem po zamknięciu pętli zdarzeń wątku (tj. Po wywołaniu QThread::quit()) pracownik zostanie automatycznie usunięty i żadne zasoby nie będą przeciekać.

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

wdrożenie pracownika:

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

Następnie:

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

Należy pamiętać, że podłączenie do QThread::started() byłoby racy: dyspozytor wydarzenie nie istnieje aż pewnego kodu w QThread::run() miał szansę na wykonanie. Musimy więc poczekać, aż wątek się tam dostanie, dzięki temu - bardzo prawdopodobne jest, że wątek roboczy przejdzie wystarczająco daleko w ciągu jednego lub dwóch zbiorów. Dzięki temu nie zmarnuje zbyt wiele czasu.

+0

Bardzo interesujące podejście! Przetestuję to jutro (po zaadaptowaniu go do mojego kodu) i postaram się przekazać odpowiednie opinie :) Hmmm, świetnie jest mieć status-machine wewnątrz wątku! Przepraszam, jestem taki entuzjastyczny, ale jestem początkującym w Qt, więc za każdym razem, gdy widzę coś nowego (dla mnie), zdaję sobie sprawę, jak ogromne możliwości oferuje Qt/C++ ...! Chciałbym zobaczyć więcej takich przykładów! Co mogę powiedzieć? Bravo, świetny pomysł! –

+0

Czy możesz rozwinąć nieco więcej: "Zauważ brak precyzyjnego zarządzania pamięcią, użycie własnych wskaźników do klas Qt jest przedwczesną optymalizacją i powinno się jej unikać, gdy jest to niepotrzebne." – Mitch

+1

@Mitch Ręczne zarządzanie pamięcią to miejsce, w którym coś 'nowego' należy przypisać do surowego wskaźnika C, a następnie ręcznie je usunąć. Kod nie posiada * * jawnej alokacji pamięci przez 'new', ale gdyby je miał, wyniki byłyby natychmiast przypisane do inteligentnych wskaźników lub utworzonych potomków w drzewie' QObject'. Zauważ, że 'QObject' działa jako zbiór inteligentnych wskaźników dla innych' QObject'ów. –

3

Miałem trochę czasu w tych dniach i przeprowadziłem dochodzenie ...
Cóż, ja przeglądałem "http://doc.qt.io/qt-5/qsslsocket.html" i znalazłem to:

bool QSslSocket::waitForEncrypted(int msecs = 30000) 

Ku mojemu wstyd, że nie zauważył tego wcześniej ... :(
pewno trzeba kupić kilka szklanek (niestety, to nie żart!)
Jestem skłonny zmodyfikować mój kod, aby go przetestować (w poniedziałek @ biuro).
Dosyć prawdopodobne, że to zadziała Co powiesz: czy wykona to zadanie?
Tak, trochę dziwnie, aby odpowiedzieć na moje własne pytanie, ale może to jest rozwiązanie, s o Zdecydowałem się podzielić :)