2015-07-11 7 views
12

Возможно, название вопроса не очень явное. Я использую Qt5 для Windows7.Qt5: Как ждать сигнала в потоке?

В потоке (QThread) в какой-то момент, в функции/методе "process()", я должен подождать "encrypted()" СИГНАЛ, принадлежащий QSslSocket, который я использую в этом потоке. Кроме того, я полагаю, я должен использовать QTimer и ждать "timeout()" СИГНАЛ, чтобы избежать получения заблокирован в бесконечном цикле ...
То, что я сейчас:

// 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()", но не более - допустим, 10 секунд - чтобы избежать застревания в этом цикле ожидания/потоке?

ответ

21

Вы можете использовать локальный цикл обработки событий, чтобы ждать сигнала, издаваемого:

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

Здесь он ждет, пока encrypted не испускается или тайм-аут достигает.

+0

Очень хорошая идея. Я тестировал его (с некоторыми изменениями в соответствии с моим кодом и целью), и он работает нормально. Я проголосовал, конечно :) –

4

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

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

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

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

+0

Очень интересный подход! Я буду тестировать его завтра (после его адаптации в соответствии с моим кодом), и я постараюсь предоставить соответствующую обратную связь :) Хм, просто здорово, что у вас есть статусная машина внутри потока! Извините, я так восторжен, но я новичок в Qt, поэтому каждый раз, когда я вижу что-то новое (для меня), я понимаю, какие огромные возможности Qt/C++ могут предложить ...! Мне жаль, что я не вижу таких примеров! Что я могу сказать? Браво, отличная идея! –

+0

Можете ли вы подробнее рассказать об этом: «Обратите внимание на отсутствие явного управления памятью. Использование указателей на классы Qt является преждевременной оптимизацией, и его следует избегать там, где это необходимо». – Mitch

+1

@Mitch Ручное управление памятью - это то, где вы «что-то новое» и назначаете его необработанному C-указателю, а затем вручную его удалять. В коде нет * * выделенного выделения ячеек с помощью 'new', но если бы они были у них, результаты были бы сразу же назначены интеллектуальным указателям или сделаны дочерними в дереве' QObject'. Обратите внимание, что 'QObject' действует как коллекция интеллектуальных указателей для других' QObject'. –

3

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

bool QSslSocket::waitForEncrypted(int msecs = 30000) 

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