Reputation: 6449
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 slave
s. The owner
needs to tell different slave
s to do different things (A, B or C) depending on user input. The slave
s are stateful objects, so I cannot see an easy way of using concurrency functions (eg, std::async
or QtConcurrency
).
Upvotes: 3
Views: 1894
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
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 ¶meter) {
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
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, QThread
class 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