PDA

View Full Version : Multi-threading in Console QCoreApplication



Dazed
1st July 2010, 21:25
Hello

I am trying to educate myself in how QT Threads can be used in a Console-based application. On each QThread I'm using QNetworkAccessManager to post SOAP messages and process responses to target systems - this to simulate a monitoring requirement I have.

I have this working, but not as I expected. Rather than the send and receive events being handled on specific threads they all seem to be happening on main thread.

Here's my output (apologies haven't used this forum before, so not sure how to make code standout):



Thread [0x1550] started running
Main Thread [0x1578] Starting main Application event loop
Thread [0xb74] started running
Thread [0x1578] Send [Data Sent]
Thread [0x1578] Send [Data Sent]
Thread [0x1578] Response [Data back]
Thread [0x1578] Response [Data back]
Thread [0x1578] Send [Data Sent]
Thread [0x1578] Send [Data Sent]
Thread [0x1578] Response [Data back]
Thread [0x1578] Response [Data back]


I have an app.exec() in main() [main thread]
and an exec() in each created thread
and am using singnals and slots to start the sending, process network response and QTimer to pause before next send...ad infinitum
My suspicion is that I haven't got my signals and slots correctly setup and all events are being managed by the app.exec() and my actual threads are effectively orphaned. Am I missing something?

I'm doing this on Windows Vista and QT 4.6.2 if that makes any difference.

Here's my code:

main() function



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

// start two Threads
for (int i=0; i<2; i++) {
opMyThread[i] = new MyThread();
opMyThread[i]->start();
}
if (true) { QMutexLocker Lock(&gmOutput);
std::cout << "Main Thread [" << app.thread()->currentThreadId() << "] Starting main Application event loop" << std::endl;
}

return app.exec();
}


The QThread run() function - which I think is the code that


void MyThread::run() {

if (true) { QMutexLocker Lock(&gmOutput);
std::cout << "Thread [" << currentThreadId() << "] started running" << std::endl;
}

connect(this, SIGNAL(readyToSend()), this, SLOT(sendData()));

// kicks off the send/receive event cycle
emit readyToSend();

exec();
}


send data function - supposed to be called on the thread, but appears to be called from main thread



void MyThread::sendData() {

//had problems with creating QNetworkAccessManager anywhere else (child/parent assertions)
if (opNetManager == 0) {
opNetManager = new QNetworkAccessManager();
connect(opNetManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
}

QByteArray sendData = "<Data>Data Sent</Data>";

if (true) { QMutexLocker Lock(&gmOutput);
std::cout << "Thread [" << currentThreadId() << "] Send [Data Sent]" << std::endl;
}

QNetworkRequest rqst(QUrl("http://localhost:8080/mytarget"));

rqst.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml");
rqst.setRawHeader("User-Agent", "MyOwnBrowser 1.0");

opNetManager->post(rqst, sendData);
}


and finally the slot function to handle network replys



void MyThread::replyFinished(QNetworkReply* reply) {

QByteArray data = reply->readAll();

if (reply->error() == QNetworkReply::NoError) {

QTextStream out(&data);
QString replyString;
replyString.append(data);

if (true) { QMutexLocker Lock(&gmOutput);
std::cout << "Thread [" << currentThreadId() << "] ";
std::cout << "Response [" << replyString.toAscii().constData() << "]" << std::endl;
}
} else {
if (true) { QMutexLocker Lock(&gmOutput);
std::cout << "Thread [" << currentThreadId() << "] ";
std::cout << "Response [" << reply->error() << "]" << std::endl;
std::cout << "Response [" << reply->errorString().toAscii().constData() << "]" << std::endl;
}

}
reply->deleteLater();

// emit the send again with wait
QTimer::singleShot(2000, this, SIGNAL(readyToSend()));
}


Any help or education greatly received.

grabalon
2nd July 2010, 00:41
You probably need: moveToThread(this) in the constructor for 'MyThread' otherwise your 'this' pointer will be referencing an object "owned" by the main thread, and all signals and slots will be tied to there.

Dazed
2nd July 2010, 07:44
That's it !, many thanks :)

Now this outputs...


Thread [0x1438] started running
Main Thread [0x430] Starting main Application event loop
Thread [0x11a8] started running
Thread [0x1438] Send [Data Sent]
Thread [0x11a8] Send [Data Sent]
Thread [0x1438] Response [Data back]
Thread [0x11a8] Response [Data back]
Thread [0x1438] Send [Data Sent]
Thread [0x11a8] Send [Data Sent]
Thread [0x11a8] Response [Data back]

tbscope
2nd July 2010, 07:55
http://labs.trolltech.com/blogs/2010/06/17/youre-doing-it-wrong/

Dazed
4th July 2010, 15:27
That's a very interesting blog and set of comments. Think "wrong" may be a little harsh especially given documentation pointers. Certainly helps explain a little more about what is going on and the importance of QObject::moveToThread().

So I have re-factored my code to create a separate Worker object and QThread object then move the Worker to the QThread.

In doing this I have noticed a couple of potential gotchas - which I thought I'd share.

#1: It 's important to create objects 'owned' by the Worker as children. So to use "new QNetworkAccessManager(this);" as the documentation for Object::moveToThread() states "Changes the thread affinity for this object and its children".

#2: The timing of when QNetworkAccessManager seems to be important.
When I create the QNetworkAccessManager in the Worker object constructor (with (this) as above) then I get:


Thread [0x1380] Send [Data Sent]
QObject::startTimer: timers cannot be started from another thread
Thread [0x1380] Response [Data back]


The only way that I can get this to work is to create the QNetworkAccessManager and form the connect for QNetworkAccessManager::finished() is just before first use.

By this time of course the Worker object has been 'moved' and the Thread is active. So any new objects appear to have correct affinity. BTW - doesn't seem to matter if it's created with (this) or not.

So I have it working in both modes (over-riding QThread and separate objects). Both of these points relate to both modes. However for #2 my solution feels a little like a work-around - so I may of course have missed something else here.