Super-intelligent Shade
Super-intelligent Shade

Reputation: 6449

Correct way to signal QObject on another thread?

Let's say I have a slave object that lives in another thread. I want to tell it to do A, B, C on that thread. I can think of 3 ways of doing it:

(1) Using QTimer::singleShot

(2) Using QMetaObject::invokeMethod

(3) Creating another master object and connecting its signal to the slave

Following is an example:

class slave : public QObject
{
    QThread thread_;

    friend class master;
    void do_A(params);
    void do_B(params);
    void do_C(params);

public:
    slave() { thread_.start(); moveToThread(&thread_); }
    ~slave() { thread_.quit(); thread_.wait(); }

    void que_A(params) { QTimer::singleShot(0, [&](){ do_A(params); }); } // (1)

    void que_B(params) { QMetaObject::invokeMethod(this, "do_B", params); } // (2)
}

class master : public QObject // (3)
{
    Q_OBJECT

public:
    master(slave* s) { connect(this, &master::que_C, s, &slave::do_C); }

    void do_C(params) { emit que_C(params); }

signals:
    void que_C(params);
}

My concerns are:

(1) I am abusing QTimer.

(2) Using strings for signals/slot is so qt4. Qt5 uses new syntax.

(3) Too much boilerplate.

Is any of the methods considered more correct compared to the others? Or can anybody think of a better way?

Please include your reasoning (not just opinion) why one method should be chosen over others.

UPDATE:

In my real-world application I have another class -- let's call it owner -- that owns several slaves. The owner needs to tell different slaves to do different things (A, B or C) depending on user input. The slaves are stateful objects, so I cannot see an easy way of using concurrency functions (eg, std::async or QtConcurrency).

Upvotes: 3

Views: 1894

Answers (3)

WindyFields
WindyFields

Reputation: 2875

Well, I will not comment (1) and (2) but I must say that (3) is the one usually used. However, I understand your concern about too much boilerplate. After all, creating a separate signal, say doActionA(), connecting it via QueuedConnection to some real actionA() and at last emitting it... too much noise and useless moves.

Indeed, the only benefit it gives you is a loose coupling (you can send a signal being not aware of existence of slots connected to it). But if I create a signal with a name doActionA() of course I am aware of actionA() existence. So then the question is starting to raise "Why do I have to write all this stuff?"

Meanwhile, Qt kind of provides the solution to this problem giving you the ability to post your own events to any event loop (and as you know QThread has one). So, implement it once and you do not need to write a lot of connect emit stuff any more. Also, I suppose I is more efficient because all in all, every slot invokation via QueuedConnection just posts an event in an event loop.

Here InvokeAsync posts the event for member function execution into the event loop of the thread where QObject lives:

#include <QCoreApplication>
#include <QEvent>
#include <QThread>

#include <string>
#include <iostream>
#include <type_traits>
#include <functional>

template<typename T, typename R, typename ... Params, typename... Args>
void InvokeAsync(T* object, R (T::*function)(Params...), Args&&... args)
{
    struct Event : public QEvent
    {
        std::function<R ()> function;
        Event(T* object, R (T::*function)(Params...),  Args&& ... args)
            : QEvent{ QEvent::None },
              function{ std::bind(function, object, std::forward<Args>(args)...) }
        {
        }
        ~Event() { function(); }
    };

    QCoreApplication::postEvent(object, new Event{ object, function, std::forward<Args>(args)... });
}

struct Worker : QObject
{
    void print(const std::string& message, int milliseconds)
    {
        QThread::currentThread()->msleep(milliseconds);
        std::cout << message
                  << " from thread "
                  << QThread::currentThreadId() << std::endl;
    }
};

int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);

    std::cout << "GUI thread " << QThread::currentThreadId() << std::endl;

    QThread thread;
    thread.start();

    Worker worker;
    worker.moveToThread(&thread);

    InvokeAsync(&worker, &Worker::print, "Job 1", 800);
    InvokeAsync(&worker, &Worker::print, "Job 2", 400);
    InvokeAsync(&worker, &Worker::print, "Job 3", 200);

    a.exec();

    return 0;
}

Output:

GUI thread 00000000000019C8
Job 1 from thread 00000000000032B8
Job 2 from thread 00000000000032B8
Job 3 from thread 00000000000032B8

As you see all jobs where done in a different thread in order of their invocation. Qt guarantees that events with the same priority are processed in order as they were posted.

Also it is OK, if worker lives in GUI thread. Events will be just posted in GUI event loop and processed later (that is why I called those Async).

If you see any mistakes or have some remarks, please, write in comments and we will figure it out.

Upvotes: 2

The Quantum Physicist
The Quantum Physicist

Reputation: 26256

In fact, the Qt documentation provides a very nice example that probably answers your question on how to use QThread. It's the following:

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};

The idea here is simple. You have a worker, which is running in the thread. You also have the controller, which is basically your main thread that submits work to the other thread. The thread will (thread-safely) emit the resultReady() signal when it's finished. Signals and slots are thread-safe here, as long as they use Qt::QueuedConnection to communicate.

Upvotes: 1

Antoine Morrier
Antoine Morrier

Reputation: 4078

I am not pretty sure I understood well the question. So I will explain here how to manage thread in Qt (in summarized way).

First, QThreadclass is not really meant to be inherited from. Instead, make something like this :

class Slave : public QObject {
    Slave(QThread *thread) {moveToThread(thread);)
};

QThread thread;
Slave slave(&thread);

After, you normally can use signal and slots in the normal way.

However, if your objective is only to "run a function inside another thread", maybe QConcurrent could be a better way?

Upvotes: 1

Related Questions