У меня есть класс, который должен работать в потоке и нуждается в цикле событий для слотов, в настоящее время я хорошо запускаю его с moveToThread()
, но я хотел бы использовать QThreadPool
и я столкнулся с проблемой.
Когда бегать с QThreadPool
run()
метод моего runnable вызывается из пула потоков (я проверяю это с QThread::currentThread()
), но мои слоты не работают в потоке пула, поэтому я думаю, что объект не перемещен в поток в пуле.
Я думаю, что это потому, что я знаю, что слоты запускаются в потоке получателя, и это именно то (правильное) поведение, которое я получаю при использовании moveToThread()
метод и QThread
,
Как мне получить мой QRunnable
(Foo в приведенном ниже примере), чтобы работать полностью в пулах?
Или я что-то не так делаю или неправильно понял?
Следующий POC демонстрирует проблему:
foo.h
#ifndef FOO_H
#define FOO_H
#include <QObject>
#include <QRunnable>
#include <QEventLoop>
class Foo : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit Foo(int data, QObject *parent = 0);
void run();
signals:
void startWorking();
public slots:
void doWork();
private:
QEventLoop eventLoop;
int data;
};
#endif // FOO_H
foo.cpp
#include "foo.h"
#include <QThread>
#include <QDebug>
Foo::Foo(int d, QObject *parent) :
QObject(parent), eventLoop(this), data(d)
{
}
void Foo::run()
{
qDebug() << "run() in: " << QThread::currentThread();
connect(this, SIGNAL(startWorking()), this, SLOT(doWork()));
emit startWorking();
eventLoop.exec();
}
void Foo::doWork()
{
qDebug() << "doWork() in: " << QThread::currentThread();
}
main.cpp
#include <QCoreApplication>
#include <QThreadPool>
#include "foo.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Foo *foo = new Foo(42);
QThreadPool::globalInstance()->start(foo);
return a.exec();
}
Обратите внимание, однако, что в моем реальном коде сигнал не будет передаваться сразу, потому что это произойдет после того, как я получу некоторые данные в сети.
PS: POC также можно найти Вот.
Может быть, вы могли бы разделить свою логику в class Foo
на две части:
хостер QRunnable
с QEventLoop
и рабочий QObject
, который вы создаете в рабочем потоке в run()
перед звонком QEventLoop::exec
метод. Затем вы перенаправляете все сигналы на рабочий объект.
Так что теперь ваши слоты будут вызываться в потоке пула.
Тем не мение, QThreadPool
предназначен для выполнения множества коротких задач без создания слишком большого количества одновременных потоков. Некоторые задачи поставлены в очередь и ждут завершения других. Если это не ваше намерение, вы можете вернуться к старому доброму QThread
и использовать его вместо этого.
Вы можете поддерживать оба режима, но это потребует некоторой координации извне. Моя стратегия — излучать сигнал изнутри. QRunnable::run
прохождение текущего потока. Когда вы планируете использовать его в пуле потоков, используйте Qt::BlockingQueuedConnection
на этот сигнал и сделай свой moveToThread
там. В противном случае переместите его в QThread
и подать сигнал, чтобы начать работать как обычно.
TaskRunner.h
#pragma once
#include <QObject>
#include <QRunnable>
#include <QThread>
class TaskRunner : public QObject, public QRunnable
{
Q_OBJECT
public:
TaskRunner(int data, QObject* parent = nullptr);
void run() override;
Q_SIGNALS:
void start();
void starting(QThread*);
void stop();
private:
int data;
};
TaskRunner.cpp
#include "TaskRunner.h"#include <QEventLoop>
#include <stdexcept>TaskRunner::TaskRunner(int data, QObject* parent)
: QObject(parent), data(data)
{
// start should call run in the associated thread
QObject::connect(this, &TaskRunner::start, this, &TaskRunner::run);
}
void TaskRunner::run()
{
// in a thread pool, give a chance to move us to the current thread
Q_EMIT starting(QThread::currentThread());
if (thread() != QThread::currentThread())
throw std::logic_error("Not associated with proper thread.");
QEventLoop loop;
QObject::connect(this, &TaskRunner::stop, &loop, &QEventLoop::quit);
// other logic here perhaps
loop.exec();
}
main.cpp
#include <QCoreApplication>
#include <QThreadPool>
#include "TaskRunner.h"
// comment to switch
#define USE_QTHREAD
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
auto runner = new TaskRunner(42);
#ifdef USE_QTHREAD
// option 1: on a QThread
auto thread = new QThread(&a);
runner->moveToThread(thread);
QObject::connect(thread, &QThread::finished, runner, &QObject::deleteLater);
Q_EMIT runner->start();
// stop condition not shown
#else
// option 2: in a thread pool
QObject::connect(
runner, &TaskRunner::starting,
runner, &QObject::moveToThread,
Qt::BlockingQueuedConnection);
QThreadPool::globalInstance()->start(runner);
// stop condition not shown
#endif
return a.exec();
}
С вашего подключения звонить
connect(this, SIGNAL(startWorking()), this, SLOT(doWork()));
использовал параметр по умолчанию для типа соединения, это будет Qt :: Autoconnection.
Сигнал испускается из пула потоков, а слот по-прежнему принадлежит foo, который имеет сходство потоков с основным потоком. Автосоединение решит поместить слот в очередь событий основного потока.
Это можно исправить двумя способами:
1.
connect(this, SIGNAL(startWorking()), this, SLOT(doWork()), Qt::DirectConnection);
и удалите eventloop.exec ();
2.
в методе run переместите объект foo в текущий поток перед подключением сигнала и слота.