PDA

View Full Version : Thread related problems with QNetworkAccessmanager



Moschops
20th October 2015, 13:42
I have a QNetWorkAccessManager object created in thread 1 (main GUI thread).

Later, a different thread wishes to use it, like this:


QNetworkReply *reply = _manager->get(request_);

This results in the error message to console:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNetworkAccessManager(0x146a3c0), parent's thread is QThread(0x134f500), current thread is QThread(0x3261ca0)

I read about it, and find that I should not have the function get execute in the thread that the QNetworkAccessManager was not created in.

I then find that I can use moveToThread() to fix this. I add code:


_manager->moveToThread(QThread::currentThread());
QNetworkReply *reply = _manager->get(request_);

This does not work, with the following error to console:

QObject::moveToThread: Current thread (0x2b44b20) is not the object's thread (0x134f500).
Cannot move to target thread (0x2b44b20)

I read up on moveToThread(), and I think the problem here is that I can only do this in the thread that the QNetworkAccessManager is in. That is, I have to run the function moveToThread() in the thread that the QNetworkAccessManager is currently "in", or "has affinity with", which is probably thread one (main GUI thread). I can push the QNetworkAccessManager from that original thread to the current thread, but I cannot pull it from the current thread.

At this point, this is starting to look like a lot of work and I think I must be missing something. There must be a better, "right" way to do this. To create a QNetworkAccessManager object in the main GUI thread and then use it from other threads. What am I missing? How is this meant to be done?

yeye_olive
20th October 2015, 13:56
Indeed, there is a better way: do not use threads for your networking code.

QNetworkAccessManager is not meant to be used in different threads. Each QNetworkReply spawned by the manager emits signals when data is ready or when the request has finished. You can keep all your networking code in the main thread just by connecting to those signals. Of course, this means that your code is scattered around the program: a block of code sends the request, another block deals with the reply. But hey, this is just event-driven programming.

Why do you need threads in the first place? If you need them because of some heavy computation on the data received in the reply, you can read the reply in the main thread and pass the data to a thread, or a function run with QtConcurrent:run().

Moschops
20th October 2015, 14:04
The bigger picture is that this is part of a much larger process that takes a long long time, and this network use happens in the middle of it. To avoid freezing the GUI, this larger process happens in a different thread and provides updates to the GUI so the GUI can have a nice progress bar and tell the user interesting things about what is happening.

So it sounds like I need to have this process still happen in a different thread, but instead of having it do its own
QNetworkReply *reply = _manager->get(request_); I need this secondary, non-GUI process to emit a signal that then causes
QNetworkReply *reply = _manager->get(request_); to happen in the main thread, and have the secondary process then wait around until the reply object emits a signal saying it's finished?

yeye_olive
20th October 2015, 14:27
Yes, you can almost do that. For instance:

In the secondary thread, emit a signal, e.g. dataRequested(QUrl), when you need the data, then wait with a QEventLoop for the reply to be ready/an error to occur.

In the main thread, connect dataRequested(QUrl) to a slot that sends the request through the QNetworkAccessManager, then deal with the reply. When the reply has finished, read the data or determine the error, store this information somewhere, then unblock the secondary thread by sending a signal previously connected the the QEventLoop's quit() slot.

The secondary thread can then read the reply and check for any error, then continue its execution.

There are alternatives, e.g. using mutexes and wait conditions.

This QEventLoop-based solution is adapted to situations with a complex context, e.g. if the network request happens in the middle of a nested loop or in a recursive call. In simpler cases you can just redesign the code in the secondary thread to be event-driven, and let QThread::run() run an event loop (which is what the default implementation does).

Moschops
20th October 2015, 14:57
I think some kind of redesign may be in order. Perhaps I can find a way to get the QNetworkAccessManager call done in advance in the main thread, and just carry the information forwards into the heavy processing. That would not only solve the problem, but do it by making things simpler. Thanks very much for your words.

anda_skoa
24th October 2015, 17:17
Or you just use a network access manager that got created in the thread.
Requires a thread with running event loop though.

Cheers,
_