Слот в QObject::connect не хочет выполняться в потоке объекта, который принимает сигнал
Тяжелый класс для работы с большими директориями унаследован от QThread. В конструкторе указано только : QThread(nullptr). Вся логика лежит в перегруженном методе run(), который запускается в новом потоке после создания объекта и вызова родительского публичного метода Thread::start().
В методе run() вывожу в консоль id потока, затем в исполняемом методе. Потоки разные. Таким образом объект, выведенный для работы в отдельный поток, фактически в своем потоке ничем не занимается, а соединения сигналов/слотов к нему являются блокирующими для вызывающего потока.
QObject::connect(mainThreadLogic, &MainClass::do, anotherObjectDerivedFromQThread, &Another::do);
Пробовал переносить это соединение в run() объекта, унаследованного от QThread, но результат прежний.
UPD. Пример:
class HeavyJob : public QThread
{
public:
HeavyJob() : QThread(nullptr) {}
void run() override; // пустой внутри
public slots:
void do()
{
// Реализация в cpp-файле, но для простоты приведена тут
QThread::sleep(10); // пример тяжелой блокирующей операции
}
};
HeavyJob* heavy = new HeavyJob;
connect (someObject, &SomeObject::someSignal, heavy, &HeavyJob::do);
heavy->start();
// При сигнале someSignal() объекта someObject
// слот do() объекта heavy выполняется в потоке someObject,
// а не в специально созданном потоке объекта heavy.
// В этом гвоздь программы.
Ответы (4 шт):
Не знаю на сколько правильно, но у меня вот так получилось:
class HeavyJob : public QThread
{
Q_OBJECT
public:
HeavyJob()
: QThread(nullptr)
{}
void run() override { exec(); }
public slots:
void doAction()
{
qDebug() << __FUNCTION__ << " | Thread Id:" << QThread::currentThreadId();
QThread::sleep(2); // пример тяжелой блокирующей операции
}
};
...
HeavyJob *job = new HeavyJob();
job->moveToThread(job);
job->start();
connect(this, &MainWindow::emitSignal, job, &HeavyJob::doAction);
Вывод:
Main thread: 0x3030
HeavyJob::doAction | Thread Id: 0x545c
В данном случае даже не обязательно унаследоваться от QThread, можно сделать согласно вот этой доке
Спасибо за советы.
Вопрос решился указанием типа подключения Qt::QueuedConnection, который указывается пятым аргументов в Object::connect().
Полезную информацию нашел тут:
Автоматическое соединение (Auto Connection) (по умолчанию) Если сигнал испускается в потоке, с которым объект-получатель имеет родство, то поведение такое же, как и при прямом соединении. В противном случае, поведение аналогично соединению через очередь." Прямое соединение (Direct Connection) Слот вызывается немедленно при отправке сигнала. Слот выполняется в потоке отправителя, который не обязательно является потоком-получателем. Соединение через очередь (Queued Connection) Слот вызывается, когда управление возвращается в цикл обработки событий в потоке получателя. Слот выполняется в потоке получателя.
Все слоты QThread'а исполняются в контексте того потока, в котором находится сам объект QThread. Обычно это исходный поток, в котором QThread и был создан.
Наследовать QThread нужно чуть чаще, чем никогда, а правильным решением в данном случае будет создать отдельный объект-работника, унаследованный от QObject и переместить его в отдельный поток:
class HeavyWorker : public QObject {
public slots:
void do() {
QThread::sleep(10); // пример тяжелой блокирующей операции
}
};
// ...
QThread* thread = new QThread(this);
HeavyWorker* worker = new HeavyWorker(this);
worker->moveToThread(thread);
connect (someObject, &SomeObject::someSignal, worker, &HeavyWorker::do);
thread->start();
см. описание QThread
Вообще, для таких задач есть специальный модуль QtConcurrent
В вашем же случае, при уже запущенном и ожидающем потоке, можно делать так:
class HeavyJob : public QThread
{
signals:
void workStarted();
void workFinished();
public:
HeavyJob() : QThread(nullptr) {}
void run() override {
__action = true;
QMutex mutex;
while(__action) {
mutex.lock();
waiter.wait(&mutex);
if (!__action) {
break;
}
emit workStarted();
// Тяжелая задача
emit workFisnished();
mutex.unlock();
}
}
public slots:
void do()
{
waiter.wakeAll();
}
void stop() {
__action = false;
waiter.wakeAll();
}
private:
QWaitCondition waiter;
bool __action;
};
Теперь вы можете создать где-нибудь поток, сразу запустить его
HeavyJob* heavy = new HeavyJob;
heavy->start();
После запуска он встанет в ожидание на строчке waiter.wait() и далее, чтобы его разбудить для выполнения задачи, нужно вызвать слот do. Для прекращения работы потока - слот stop()