Qt Thread with Movetothread

qt thread with movetothread

The canonical Qt way would look like this:

 QThread* thread = new QThread( );
Task* task = new Task();

// move the task object to the thread BEFORE connecting any signal/slots
task->moveToThread(thread);

connect(thread, SIGNAL(started()), task, SLOT(doWork()));
connect(task, SIGNAL(workFinished()), thread, SLOT(quit()));

// automatically delete thread and task object when work is done:
connect(task, SIGNAL(workFinished()), task, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

thread->start();

in case you arent familiar with signals/slots, the Task class would look something like this:

class Task : public QObject
{
Q_OBJECT
public:
Task();
~Task();
public slots:
// doWork must emit workFinished when it is done.
void doWork();
signals:
void workFinished();
};

moveToThread vs deriving from QThread in Qt

I would focus on the differences between the two methods. There isn't a general answer that fits all use cases, so it's good to understand exactly what they are to choose the best that fits your case.

Using moveToThread()

moveToThread() is used to control the object's thread affinity, which basically means setting the thread (or better the Qt event loop) from which the object will emit signals and its slots will be executed.

As shown in the documentation you linked, this can be used to run code on a different thread, basically creating a dummy worker, writing the code to run in a public slot (in the example the doWork() slot) and then using moveToThread to move it to a different event loop.

Then, a signal connected to that slot is fired. Since the object that emits the signal (the Controller in the example) lives in a different thread, and the signal is connected to our doWork method with a queued connection, the doWork method will be executed in the worker thread.

The key here is that you are creating a new event loop, run by the worker thread. Hence, once the doWork slot has started, the whole event loop will be busy until it exits, and this means that incoming signals will be queued.

Subclassing QThread()

The other method described in Qt's documentation is subclassing QThread. In this case, one overrides the default implementation of the QThread::run() method, which creates an event loop, to run something else.

There's nothing wrong with this approach itself, although there are several catches.

First of all, it is very easy to write unsafe code, because the run() method is the only one in that class that will be actually run on another thread.

If as an example, you have a member variable that you initialize in the constructor and then use in the run() method, your member is initialized in the thread of the caller and then used in the new thread.

Same story for any public method that could be called either from the caller or inside run().

Also slots would be executed from the caller's thread, (unless you do something really weird as moveToThread(this)) leading to extra confusion.

So, it is possible, but you really are on your own with this approach and you must pay extra attention.

Other approaches

There are of course alternatives to both approaches, depending on what you need. If you just need to run some code in background while your GUI thread is running you may consider using QtConcurrent::run().

However, keep in mind that QtConcurrent will use the global QThreadPool. If the whole pool is busy (meaning there aren't available threads in the pool), your code will not run immediately.

Another alternative, if you are at the least on C++11, is to use a lower level API such as std::thread.

Qt thread ID is equal to MainWindows? (moveToThread)

There are several issues that I'll address in random order.

First of all, using thread IDs is bad user experience. Give the threads a descriptive name:

int main(...) {
QApplication app(...);
QThread myThread;
MyObject myObject;
myObject->moveToThread(&myThread);
QThread::currentThread()->setObjectName("mainThread");
myThread.setObjectName("myThread");
...
}

Then use QThread::currentThread()->objectName() to retrieve it. You can also pass QObject* to qDebug() to display the name of the thread:

qDebug() << QThread::currentThread();

Your signal invocation would then become:

QString currentThreadName() {
return QThread::currentThread()->objectName().isEmpty() ?
QStringLiteral("0x%1").arg(QThread::currentThread(), 0, 16) :
QThread::currentThread()->objectName();
}

...
emit requestFileContent(
QStringLiteral("Emitting from thread \"%1\"\n").arg(currentThreadName));

Then, use the above to deal with the thread you've created:

    auto thread = new QThread(this);
thread->setObjectName("fileThread");
ui.textBrowser->append(QStringLiteral("Worker thread: \"%1\").arg(thread->objectName()));
auto AVC_file = new AVC_File;

AVC_file->moveToThread(thread);
...

But AVC_FileCheck is invoked from the main thread. Whether that's OK or not depends on how that method is implemented. It needs to be thread-safe, see this question for a discussion of that. TL;DR: The following pattern could be a starting point:

class AVC_file : public QObject {
Q_OBJECT
Q_SLOT void fileCheck_impl(QIODevice * dev) {
dev->setParent(this);
...
}
Q_SIGNAL void fileCheck_signal(QIODevice *);
public:
void fileCheck(QIODevice *dev) { fileCheck_signal(dev); }
AVC_file(QObject *parent = nullptr) : QObject(parent) {
connect(this, &AVC_file::fileCheck_signal, this, &AVC_file::fileCheck_impl);
...
}
};

Finally, your existing AVC_fileCheck API is broken. You pass QFile by reference: this won't ever work since it ceases to exist as soon as on_OpenAVCFile_clicked returns. When AVC_file uses that file in its thread, it's a dangling object reference.

Instead, you must pass the ownership of the file to AVC_file, and pass a pointer to an instance that AVC_file will dispose when done with. Or simply let AVC_file open the file for you!

Qt: move to thread

It will live in the main thread, since you're calling worker->init() from the main thread. You can either use signals and slots to call init from the worker thread, or use QMetaObject::invokeMethod with a queued connection (You don't have to specify this, as it will use Qt::AutoConnection by default and that will use Qt::QueuedConnection if invokeMethod is called from a different thread than the receiving object).

QMetaObject::invokeMethod(worker, "init",
Qt::QueuedConnection);

You can also create myObject in the constructor and set this as the parent. Then when you call moveToThread, the object will also move its children to the same thread.

QObject::moveToThread: Changes the thread affinity for this object and its children.

Qt moveToThread: What resources are brought with the object?

As the Qt documentation for QObject::moveToThread states: -

Changes the thread affinity for this object and its children. The object cannot be moved if it has a parent. Event processing will continue in the targetThread.

In this case, a parent is an object whose child is set either by passing the parent in the constructor or by calling setParent on the child. It is not an an object which has a pointer to another object.

In the code, if I never set b's parent to a, and if I call movetothread() to move a into a worker thread, will b be moved into that thread too?

So, no, if b's parent is not set and you call moveToThread on 'a', 'b' will still have the original thread affinity.

If it is not moved, if I call b.init() from the worker thread...

If you've moved 'a' and not 'b' to a worker thread, then you should not be calling b.init directly from the worker thread. Instead, the object in the worker thread ('a') should emit a signal for an object in the original thread to call b.init from a connected slot

Create a QObject in another thread and retrieve it to the current thread = ASSERT failure in Debug on msvc16

So I debugged qt lib, the problem comes from setParent() doing a sendEvent() without checking the thread affinity. The 2 objects lives in the same thread but the setParent() called is done from another one. Even though my operation isn't typical, it's still valid.. It's a simple bug or at least an unhandled case.

Depending on the calling thread it should do a postevent() instead.

Sample Image

Finally I've just replaced obj->setParent(&mainObj) by

QMetaObject::invokeMethod(&mainObj, [&mainObj, obj](){
obj->setParent(&mainObj);
});

This call is automatically done with a Queued connection and finally executed in the correct thread. Of course we have to start the eventloop in the main thread to retrieve this queued event.

This is an acceptable workaround

#include <QApplication>
#include <QDebug>
#include <QThread>
#include <QTimer>

class Thread : public QThread //Just a convenient Class using a lambda
{
public:
Thread::Thread(QObject *parent = nullptr) : QThread(parent){}
std::function<void()> todo;
protected:
virtual void run() override{
todo();
}
};

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

QObject mainObj;

Thread thread;
thread.todo = [&](){
//QObject *obj = new QObject(&mainObj); //"QObject: Cannot create children for a parent that is in a different thread." ! Of course!

// So we try this
QObject *obj = new QObject;

qDebug()<<obj->thread();
obj->moveToThread(mainObj.thread());
qDebug()<<obj->thread(); //Check that the Thread affinity change is done

QMetaObject::invokeMethod(&mainObj, [&mainObj, obj](){
obj->setParent(&mainObj);
});

QObject::connect(obj, &QObject::destroyed, [](){ //Parent mecanism is OK
qDebug()<<"Child destroyed";
});
};
thread.start();
thread.wait();

QTimer::singleShot(0,[](){
qApp->quit();
});
return a.exec(); //Add an event loop for the connect (queued) from the thread -> setParent() triggers a SendEvent() in the main thread where the two object now live
}


Related Topics



Leave a reply



Submit