Выполнить слоты внутри QThreadPool

У меня есть класс, который должен работать в потоке и нуждается в цикле событий для слотов, в настоящее время я хорошо запускаю его с 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 также можно найти Вот.

3

Решение

Может быть, вы могли бы разделить свою логику в class Foo на две части:
хостер QRunnable с QEventLoopи рабочий QObject, который вы создаете в рабочем потоке в run() перед звонком QEventLoop::exec метод. Затем вы перенаправляете все сигналы на рабочий объект.
Так что теперь ваши слоты будут вызываться в потоке пула.
Тем не мение, QThreadPool предназначен для выполнения множества коротких задач без создания слишком большого количества одновременных потоков. Некоторые задачи поставлены в очередь и ждут завершения других. Если это не ваше намерение, вы можете вернуться к старому доброму QThread и использовать его вместо этого.

2

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

Вы можете поддерживать оба режима, но это потребует некоторой координации извне. Моя стратегия — излучать сигнал изнутри. 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();
}
0

С вашего подключения звонить

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 в текущий поток перед подключением сигнала и слота.

-1
По вопросам рекламы [email protected]