hedgar2017
hedgar2017

Reputation: 1657

Creating QNetworkAccessManager in another thread

I have a QNetworkAccessManager created in another thread. The network is meant to be used only in MyMegaThread.
QNetworkAccessManager is created from the thread's run method:

mp_manager.reset(new QNetworkAccessManager{this});

On creation I get such a message in console:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is MyMegaThread(0x237eabd0ee0), parent's thread is QThread(0x237e70742a0), current thread is MyMegaThread(0x237eabd0ee0)

This message is totally harmless, but I wonder which parent the manager is supposed to have.
I suspect it happens because the MyMegaThread instance is created in the main thread, but I need a parent created in MyMegaThread instead.

What is an idiomatic way of doing this?

Upvotes: 1

Views: 2357

Answers (2)

No, the message is not harmless at all. The object you have created has a null parent and no reference on the thread association and thus its thread() method may return a dangling pointer at any time. It cannot safely use timers nor receive cross-thread calls. It is basically as useless object, and you're asking for undefined behavior to strike. This shouldn't be a warning, but a failure. Qt did you a disservice here by allowing you to continue.

The idiomatic way of doing it is first of all not to derive from QThread. QThread is a thread handle. It wraps a system resource. Put all of your functionality into a regular QObject moved into a QThread. The idiomatic way to endlessly "do stuff" on any thread, including the main thread, is to use a zero-duration timer. Note that zero-duration timers have nothing to do with timing at all. They are essentially event loop handles, calling them a timer is a misnomer.

To wit:

// https://github.com/KubaO/stackoverflown/tree/master/questions/thread-simple-50632807
#include <QtNetwork>

class Thread final : public QThread {
   Q_OBJECT
public:
   void takeObject(QObject *obj) {
      obj->moveToThread(this);
   }
   ~Thread() override {
      requestInterruption();
      quit();
      wait();
   }
};

class Class : public QObject {
   Q_OBJECT
   QBasicTimer m_workTimer;
   QNetworkAccessManager m_manager{this};
   void doWorkChunk() {
      qDebug() << "tick...";
      QThread::sleep(1); // emulate a blocking operation
   }
protected:
   void timerEvent(QTimerEvent *ev) override {
      if (ev->timerId() != m_workTimer.timerId())
         return;
      doWorkChunk();
   }
public:
   explicit Class(QObject *parent = {}) : QObject(parent) {
      m_workTimer.start(0, this);
   }
};

int main(int argc, char *argv[]) {
   QCoreApplication app(argc, argv);
   Class object;
   Thread workThread;
   workThread.start();
   workThread.takeObject(&object);
   QTimer::singleShot(3000, &QCoreApplication::quit);
   return app.exec();
}
#include "main.moc"

The QBasicTimer::stop: Failed. Possibly trying to stop from a different thread warning is relatively benign and indicates an internal timer handle leak. For a workaround, see this answer.

Upvotes: 1

JustWe
JustWe

Reputation: 4484

Parent is MyMegaThread(0x237eabd0ee0), parent's thread is QThread(0x237e70742a0), current thread is MyMegaThread(0x237eabd0ee0)

The issue does not relate to QNetworkAccessManager.

Here is the demo to reproduce the warning.

#include <QDebug>
#include <QThread>

class MyMegaThread : public QThread
{
    Q_OBJECT
public:
    using QThread::QThread;

protected:
    void run() override {
        qDebug()<<QThread::currentThread()<<this->thread();
        new QObject(this);
    }
};

// main
MyMegaThread m;
m.start();

Output:

MyMegaThread(0x60fe18) QThread(0x16a7c48)

It's rule of QObject:

All QObjects must live in the same thread as their parent. Consequently:

setParent() will fail if the two QObjects involved live in different threads. When a QObject is moved to another thread, all its children will be automatically moved too. moveToThread() will fail if the QObject has a parent. If QObjects are created within QThread::run(), they cannot become children of the QThread object because the QThread does not live in the thread that calls QThread::run().

http://doc.qt.io/qt-5/qobject.html#thread-affinity

Have to make sure this code new QObject running QThread be same as given parent QObject thread.

mp_manager.reset(new QNetworkAccessManager{this});

Upvotes: 3

Related Questions