Циклы событий и обработка слотов сигналов при использовании многопоточности в Qt

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

Я добавил несколько комментариев в раздел OUTPUT, но, как вы можете видеть, я не уверен, что мои предположения о том, что вызвало наблюдаемое поведение, верны. Также я не уверен, если case 3 это то, что может быть использовано в реальном коде. Вот мой тестовый код (только main.cpp отличается для каждого случая):

worker.h:

#include <QObject>
#include <QDebug>
#include <QThread>

class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0) { this->isRunning_ = false;}
bool isRunning() const { return isRunning_; }

signals:
void processingFinished();
void inProgress();

public slots:
void process()
{
this->isRunning_ = true;
qDebug() << this << "processing started";
for (int i = 0; i < 5; i++)
{
QThread::usleep(1000);
emit this->inProgress();
}
qDebug() << this << "processing finished";
this->isRunning_ = false;
emit this->processingFinished();
}

private:
bool isRunning_;
};

workermanager.h:

#include "worker.h"
class WorkerManager : public QObject
{
Q_OBJECT
public:
explicit WorkerManager(QObject *parent = 0) :
QObject(parent) {}

public slots:
void process()
{
QThread *thread = new QThread();
Worker  *worker = new Worker();

connect(thread,SIGNAL(started()),worker,SLOT(process()));
connect(worker,SIGNAL(processingFinished()),this,SLOT(slot1()));
connect(worker,SIGNAL(inProgress()),this,SLOT(slot2()));
worker->moveToThread(thread);

qDebug() << "starting";
thread->start();
QThread::usleep(500);
while(worker->isRunning()) { }
qDebug() << "finished";
}

void slot1() { qDebug() << "slot1"; }
void slot2() { qDebug() << "slot2"; }
};

main.cpp (случай 1 — нет отдельного потока для workerManager):

#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

WorkerManager* workerManager = new WorkerManager;
workerManager->process();
qDebug() << "end";
return a.exec();
}

ВЫХОД — оба slot1 а также slot2 называется в a.exec() (??? — используя основной цикл обработки событий?):

starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
slot2
slot2
slot2
slot2
slot2
slot1

main.cpp (случай 2 — workerManager перенесен в отдельную ветку, но не запущен):

#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
workerManager->process();
qDebug() << "end";
return a.exec();
}

ВЫХОД — ни slot1 ни slot2 был вызван — (??? цикл обработки событий, связанный с потоком, получает сигналы, но поскольку поток не был запущен, слоты не вызываются?):

starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end

main.cpp (случай 3 — workerManager перенесено в отдельную ветку workerManager::process() вызывается через workerManager->process()):

#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
thread->start();
workerManager->process();
qDebug() << "end";
return a.exec();
}

ВЫХОД — slot2 звонил пока Worker все еще выполняет его process() (???):

starting
Worker(0x197bb20) processing started
slot2
slot2
slot2
slot2
Worker(0x197bb20) processing finished
finished
end
slot2
slot1

main.cpp (случай 4 — workerManager перенесено в отдельную ветку workerManager::process() называется с помощью started() сигнал от thread):

#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
QObject::connect(thread,SIGNAL(started()),workerManager,SLOT(process()));
thread->start();
qDebug() << "end";
return a.exec();
}

ВЫХОД — все события, обработанные после достижения a.exec() (???):

end
starting
Worker(0x7f1d700013d0) processing started
Worker(0x7f1d700013d0) processing finished
finished
slot2
slot2
slot2
slot2
slot2
slot1

Спасибо за любые разъяснения.

11

Решение

Все полученные вами результаты абсолютно верны. Я постараюсь объяснить, как это работает.

Цикл событий — это внутренний цикл в коде Qt, который обрабатывает системные и пользовательские события. Цикл событий основного потока запускается при вызове a.exec(), Цикл событий другого потока запускается по умолчанию реализацией QThread::run,

Когда Qt решает, что пришло время обработать событие, он выполняет свой обработчик события. Пока работает обработчик событий, Qt не имеет возможности обработать любое другое событие (если оно не передано напрямую QApplication::processEvents() или некоторые другие методы). Как только обработчик событий завершен, поток управления возвращается в цикл обработки событий, и Qt может выполнить другой обработчик для обработки другого события.

Сигналы и слоты не совпадают с событиями и обработчиками событий в терминологии Qt. Но слоты обрабатываются циклами событий примерно так же. Если у вас есть поток управления в вашем коде (например, в main функция) вы можете выполнить любой слот сразу же, как и любую другую функцию C ++. Но когда Qt делает это, он может делать это только из цикла событий. Следует отметить, что сигналы всегда отправляются немедленно, в то время как выполнение интервала может быть отложено.

Теперь посмотрим, что происходит в каждом случае.

Случай 1

WorkerManager::process выполняется непосредственно при запуске программы. Новая тема запущена и Worker::process выполняется сразу в новой теме. WorkerManager::process продолжает выполнение до тех пор, пока Worker не будет завершен, замораживая все другие действия (включая обработку слотов) в главном потоке. После WorkerManager::process закончен, поток управления переходит к QApplication::exec, Qt устанавливает соединение с другим потоком, получает сообщения о вызове слота и последовательно вызывает их все.

Дело 2

Qt по умолчанию выполняет слоты объекта в потоке, к которому принадлежит этот объект. Основной поток не будет выполнять слоты WorkerManager потому что он принадлежит другому потоку. Однако эта тема никогда не запускается. Его цикл событий никогда не заканчивается. Призывания slot1 а также slot2 остаются навсегда в очереди Qt, ожидая, когда вы начнете поток. Печальная история.

Дело 3

В этом случае WorkerManager::process выполняется в основном потоке, потому что вы вызываете его непосредственно из основного потока. В то же время, WorkerManagerнить запущена Его цикл событий запущен и ждет событий. WorkerManager::process начинается Workerнить и выполняет Worker::exec в этом. Worker начинает посылать сигналы WorkerManager, WorkerManagerПоток практически сразу начинает выполнять соответствующие слоты. На данный момент кажется странным, что WorkerManager::slot2 а также WorkerManager::process выполняются одновременно. Но это прекрасно, по крайней мере, если WorkerManager потокобезопасен. Вскоре после Worker готово, WorkerManager::process закончено и a.exec() выполняется, но не так много для обработки.

Дело 4

Основная функция только запускается WorkerManagerнить и сразу едет a.exec(), в результате чего end как первая строка в выводе. a.exec() что-то обрабатывает и обеспечивает выполнение программы, но не выполняет WorkerManagerслоты, потому что он принадлежит другому потоку. WorkerManager::process выполнен в WorkerManagerэто поток из его цикла событий. Workerнить запущена и Worker::process начинает посылать сигналы от Workerнить к WorkerManagerэто нить. К сожалению, последний занят выполнением WorkerManager::process, когда Worker готово, WorkerManager::process также заканчивает и WorkerManagerПоток немедленно выполняет все очереди в очереди.

Самая большая проблема в вашем коде usleep и бесконечные петли. Вы почти никогда не должны использовать их при работе с Qt. Я понимаю что спать в Worker::process это просто заполнитель для некоторых реальных расчетов. Но вы должны удалить сон и бесконечный цикл из WorkerManager, использование WorkerManager::slot1 обнаружить Workerпрекращение. Если вы разрабатываете приложение с графическим интерфейсом, не было бы необходимости перемещать WorkerManager в другую ветку. Все его методы (без сна) будут выполняться быстро и не замораживать графический интерфейс.

16

Другие решения


По вопросам рекламы ammmcru@yandex.ru
Adblock
detector