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.
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 :) –