Execute slots inside a QThreadPool

qthreadpool example
qrunnable vs qthread
qthread::finished signal
pyqt5 threading
qt thread example

I have a class that should run in a thread and needs an event loop for the slots, currently I run it nicely with moveToThread(), but I'd like to use QThreadPool and I have encountered a problem.

When run with QThreadPool the run() method of my runnable is called from a pooled thread (I check this with QThread::currentThread()), but my slots aren't running in the pooled thread, so I think the object isn't moved to a thread in the pool.

I think this because I know the slots are run in the receiver's thread, which is exactly the (correct) behaviour I get when using the moveToThread() method and a QThread.

How do I get my QRunnable (Foo in the example below) to be run entirely in the pooled threads? Or is it something I'm doing wrong or understood wrong?

The following POC demonstrates the problem:

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();
}

Please note, however, that in my real code the signal won't be emitted right away, because it will be after I receive some data on the network.

PS: The POC can also be found here.

Maybe you could split your logic in class Foo into two: the hoster QRunnable with a QEventLoop, and a worker QObject, which you create on the worker thread in run() before calling QEventLoop::exec method. Then you forward all the signals to the worker object. So now your slots will be called on the pooled thread. However, QThreadPool is designed for executing lots of short tasks without creating too many simultaneous threads. Some tasks are enqueued and are waiting for others to finish. If this is not your intention, you might want to go back to good old QThread and use it instead.

signal and slots in Qthreadpool, pool->start(task); now in MyRunnable class ,only run() method is executing in one of the thread from Qthreadpool.all other slots that is have  Qt provides the signals and slots framework which allows you to do just that and is thread-safe, allowing safe communication directly from running threads to your GUI frontend. Signals allow you to .emit values, which are then picked up elsewhere in your code by slot functions which have been linked with .connect.

You can support both modes but it will require some coordination from the outside. My strategy is to emit a signal from inside QRunnable::run passing the current thread. When you plan to use it in a thread pool, use a Qt::BlockingQueuedConnection on this signal and do your moveToThread there. Otherwise, move it to the QThread and emit a signal to start working as usual.

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();
}

Multithreading Technologies in Qt, Instantiating a QThread provides a parallel event loop, allowing QObject slots Use QThreadPool::start() to put the QRunnable in the QThreadPool's run queue. void QThreadPool:: start (QRunnable *runnable, int priority = 0) Reserves a thread and uses it to run runnable, unless this thread will make the current thread count exceed maxThreadCount(). In that case, runnable is added to a run queue instead. The priority argument can be used to control the run queue's order of execution.

Since the your connect call

connect(this, SIGNAL(startWorking()), this, SLOT(doWork()));

used the default parameter for connection type, it will be a Qt::Autoconnection. The signal is emitted from the pooled thread, and the slot still belongs to foo, which has a thread affinity to the main thread. The autoconnection will decide to put the slot in the event queue of the main thread.

There are two ways you can fix this:

1.

connect(this, SIGNAL(startWorking()), this, SLOT(doWork()), Qt::DirectConnection);

and remove the eventloop.exec();

2.

in the run method, move the foo object to the current thread before connecting the signal and slot.

Qt Multithreading in C++: The Missing Article, Multithreading is a widespread programming and execution model that allows multiple threads to exist within the context Choosing between using QThreadPool and QThread Tasks that use signal/slots and therefore need the event loop. The code inside the Worker's slot would then execute in a separate thread. However, you are free to connect the Worker's slots to any signal, from any object, in any thread. It is safe to connect signals and slots across different threads, thanks to a mechanism called queued connections.

Multithreading with Qt, A QThread instance manages one thread of execution within the program. It is great to be able to handle signals and slots in our own QThread, but To execute code within threads managed by a QThreadPool, you will use  Use QThreadPool::start () to put the QRunnable in the QThreadPool 's run queue. When a thread becomes available, the code within QRunnable::run () will execute in that thread. Each Qt application has a global thread pool, which is accessible through QThreadPool::globalInstance ().

QThreads: Are You Using Them Wrong?, Runnable Pattern QThreadPool / QRunnable ○ Create a QRunnable With few exceptions, QThread methods do not run in the Thread of execution. Signal Slot Connections and Threads ○ Qt::DirectConnection ○ Slots  The prime number validation is calculated by an instance of the class PrimeRunnable subclassed from QRunnable. The actual code for the validation of the number has to be put into the run() method. The run() was made private to prevent it from being called directly on instances of PrimeRunnable (rather than via QThreadPool::globalInstance()->start()).

Qt 4.8: Threading Basics, The Qt GUI must run in this thread. sometimes be done conveniently using a timer to schedule execution of a slot at some point in the future. from QRunnable and run it in the global thread pool with QThreadPool::globalInstance​()->start()  The run() implementation is for a thread what the main() entry point is for the application. As QThread::run() is the thread entry point, it is rather intuitive to use the Usage 1. Usage 1-0. To run some code in a new thread, subclass QThread and reimplement its run() function. For example

Comments
  • Interesting, I will give this a shot.
  • +1, marked as the answer since I solved the problem, not exactly as you said it because I'm running the event loop in the worker, not in the runnable.
  • The second solution will not work, since I don't have a reference to a thread in the pool, I cannot know which thread will be assigned to do work. The first one won't work either since the signal won't be emitted right away, and since I don't have an event loop, the thread will exit after run() has returned.
  • you can't move the object to the current thread, because you can only "push" from the owner thread. Using Qt::DirectConnection is also a bad idea, as this does not guarantee that slots will be called on the worker thread (as op seems to expect)