Как правильно создать поток в Qt
Для теста, я начал реализовывать самый примитивный worker, который будет помещен в QThread, и тут же столкнулся с проблемой, а именно с данной ошибкой:
QEventLoop: Cannot be used without QApplication
Я хочу реализовать самый простой способ запуска потока с помощью worker-а. Код выглядит так:
worker.h
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject* parrent = nullptr);
Worker &operator=(const Worker &other) = delete;
Worker &operator=(Worker &&other) = delete;
Worker(const Worker &other) = delete;
Worker(Worker &&other) = delete;
virtual ~Worker() = default;
public slots:
virtual void start();
virtual void stop ();
signals:
void started();
void stopped();
};
worker.cpp
Worker::Worker(QObject* parrent) : QObject(parrent)
{
}
void Worker::start()
{
qDebug() << "Worker started";
emit started();
}
void Worker::stop ()
{
qDebug() << "Worker stopped";
emit stopped();
}
mytask.h
class MyTask : public QObject
{
Q_OBJECT
public:
explicit MyTask(QObject* parrent = nullptr);
MyTask& operator=(const MyTask& other) = delete;
MyTask& operator=(MyTask&& other) = delete;
MyTask(const MyTask& other) = delete;
MyTask(MyTask&& other) = delete;
virtual ~MyTask() = default;
virtual void start();
virtual void stop ();
signals:
void started();
void stopped();
protected:
Worker* p_worker{ nullptr };
};
mytask.cpp
MyTask::MyTask(QObject* parrent) : QObject(parrent), p_worker(nullptr)
{
}
void MyTask::stop()
{
if (p_worker) {
p_worker->stop();
}
}
void MyTask::start()
{
p_worker = new Worker();
QThread* thread = new QThread();
p_worker->moveToThread(thread);
connect(this, &MyTask::started, this, [](){qDebug() << "MyTask started";});
connect(this, &MyTask::stopped, this, [](){qDebug() << "MyTask stopped";});
connect(thread, &QThread::started, p_worker, &Worker::start, Qt::DirectConnection);
connect(p_worker, &Worker::started, this, [this](){emit started();}, Qt::DirectConnection);
connect(p_worker, &Worker::stopped, this, [this](){emit stopped();}, Qt::DirectConnection);
connect(p_worker, &Worker::stopped, thread, &QThread::quit, Qt::DirectConnection);
connect(p_worker, &Worker::stopped, p_worker, &Worker::deleteLater, Qt::DirectConnection);
connect(thread, &QThread::finished, thread, &QThread::deleteLater, Qt::DirectConnection);
thread->start();
}
Важно отметить то, что мне нужна реализация именно с использованием worker-a. При создании worker-a, я буду работать с ним при помощи сигналов. Стоит также отметить, что в этом примере все проверки на устранение повторного запуска потока и так далее игнорируются (для демонстрации упращеного кода).
Данный пример должен был бы вывести мне в качестве результата вот такое последование сообщений:
Worker started
MyTask started
Worker stopped
MyTask stopped
но я получаю:
Worker started
QEventLoop: Cannot be used without QApplication
Worker stopped
MyTask stopped
UPD Код запуска программы:
int main(int argc, char **argv)
{
QCoreApplication a(argc, argv);
MyTask task;
task.start();
QThread::sleep(1);
task.stop();
QThread::sleep(1);
return a.exec();
}
Ответы (1 шт):
Вам следует разобраться с тем как в Qt работают сигналы и слоты. Я писал когда-то статью "правильная работа с потоками в Qt" на Хабре, Вы ее легко найдете поиском.
Если говорить коротко, то для корректной работы многих сигналов и слотов Вам нужен запущенный event loop. Идея в том что когда у Вас два потока пытаются посылать сигналы друг другу, то чтобы это работало в реальности каждый из них поддерживает специальную очередь для сообщений и крутит цикл который регулярно проверяет появление в этой очереди сообщений. Это и есть event loop. Он по умолчанию создается у каждого QThread, но есть и еще один специальный event loop для всего приложения. Там живут обработчики все QObject у которых нет явно назначенного отдельного QThread (в том числе сами объекты QThread) и там обрабатываются сообщения от операционной системы. Например если Вы попытаетесь закрыть программу до того как она завершится сама, то это событие будет обработано именно там. И когда Ваши два потока завершатся, то именно этот цикл должен будет решить что пора завершать программу самостоятельно. Вот эти все connect между Worker::stopped и Worker::deleteLater - все это обрабатывается там.
Для запуска этого системного event loop (ну и подобных технических вещей) в Qt есть специальный класс QApplication и его специализации. Вам нужен, как уже заметили, минималистический QCoreApplication. А собственно запуск цикла у этого класса - это вызов функции .exec(). Который у Вас происходит после того как Вы попытались запустить event loop в QThread. Вполне закономерно что Вы получаете сообщения об ошибках - первый поток стартует еще до того как запустился системный event loop.
Как это поправить? Один из способов - запускать Ваши потоки не явным вызовом .start() а через отправку события. Эта функция не просто так сделана слотом. Последовательность действий должна быть такова:
- Вы создаете QApplication (всегда это должно быть одно из первых действий программы до создания любых QObject). Это создает в числе прочего системный event loop и позволяет помещать туда события
- Создаете свой начальный набор событий, потоков и т.п. Отправляете им событие "запустись". Оно будет помещено в очередь QApplication но реально не отправлено, т.к. event loop еще не запущен
- Зовете app.exec(). Event loop запускается и рассылает Ваши сообщения получателям, у Вас запускаются потоки и все работает
Но вообще исходя из Вашей задачи, мне кажется что Вам стоит присмотреться к QConcurrent. Грубо говоря вызывать QtConcurrent::run. Как вариант - использовать QThreadPool + QRunnable. Без использования QObject, сигналов и слотов. Тогда Вам будет не нужно QApplication и логика в целом скорее всего сильно упростится.