У меня были некоторые проблемы с использованием 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
Спасибо за любые разъяснения.
Все полученные вами результаты абсолютно верны. Я постараюсь объяснить, как это работает.
Цикл событий — это внутренний цикл в коде Qt, который обрабатывает системные и пользовательские события. Цикл событий основного потока запускается при вызове a.exec()
, Цикл событий другого потока запускается по умолчанию реализацией QThread::run
,
Когда Qt решает, что пришло время обработать событие, он выполняет свой обработчик события. Пока работает обработчик событий, Qt не имеет возможности обработать любое другое событие (если оно не передано напрямую QApplication::processEvents()
или некоторые другие методы). Как только обработчик событий завершен, поток управления возвращается в цикл обработки событий, и Qt может выполнить другой обработчик для обработки другого события.
Сигналы и слоты не совпадают с событиями и обработчиками событий в терминологии Qt. Но слоты обрабатываются циклами событий примерно так же. Если у вас есть поток управления в вашем коде (например, в main
функция) вы можете выполнить любой слот сразу же, как и любую другую функцию C ++. Но когда Qt делает это, он может делать это только из цикла событий. Следует отметить, что сигналы всегда отправляются немедленно, в то время как выполнение интервала может быть отложено.
Теперь посмотрим, что происходит в каждом случае.
WorkerManager::process
выполняется непосредственно при запуске программы. Новая тема запущена и Worker::process
выполняется сразу в новой теме. WorkerManager::process
продолжает выполнение до тех пор, пока Worker не будет завершен, замораживая все другие действия (включая обработку слотов) в главном потоке. После WorkerManager::process
закончен, поток управления переходит к QApplication::exec
, Qt устанавливает соединение с другим потоком, получает сообщения о вызове слота и последовательно вызывает их все.
Qt по умолчанию выполняет слоты объекта в потоке, к которому принадлежит этот объект. Основной поток не будет выполнять слоты WorkerManager
потому что он принадлежит другому потоку. Однако эта тема никогда не запускается. Его цикл событий никогда не заканчивается. Призывания slot1
а также slot2
остаются навсегда в очереди Qt, ожидая, когда вы начнете поток. Печальная история.
В этом случае WorkerManager::process
выполняется в основном потоке, потому что вы вызываете его непосредственно из основного потока. В то же время, WorkerManager
нить запущена Его цикл событий запущен и ждет событий. WorkerManager::process
начинается Worker
нить и выполняет Worker::exec
в этом. Worker
начинает посылать сигналы WorkerManager
, WorkerManager
Поток практически сразу начинает выполнять соответствующие слоты. На данный момент кажется странным, что WorkerManager::slot2
а также WorkerManager::process
выполняются одновременно. Но это прекрасно, по крайней мере, если WorkerManager
потокобезопасен. Вскоре после Worker
готово, WorkerManager::process
закончено и a.exec()
выполняется, но не так много для обработки.
Основная функция только запускается 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
в другую ветку. Все его методы (без сна) будут выполняться быстро и не замораживать графический интерфейс.