PDA

View Full Version : Struggling with QThread...



TemporalBeing
20th March 2009, 22:27
I've got an application that receive a lot of data over the network via a QTcpSocket. If I keep the it all in the main thread, then the processor usage spikes and memory goes through the roof as there is no time left for processing events. I am trying to spin this processing off into a new object derived from QThread, and then use signals/slots to talk with the main thread.

The derived thread primarily does the following:
1) Receives data from the QTcpSocket.
2) Validates the data
3) Prepares the data for display
4) Sends 1 piece of data to the main application.
5) Upon receipt of #4, the main application sends the old data (if any) back to the thread for destruction.



// myObjectPtr is registered with qRegisterMetaType()
// dataInfoObject is registered with qRegisterMetaType()
// it's copy constructor initializes its QObject parent to NULL.
class myThread : public QThread
{
Q_OBJECT
Q_SIGNALS:
void sendData(const myObjectPtr data);
public Q_SLOTS:
void setInfo(const dataInfoObject& _objectInfo);
void setServer(const QString& _hostAddress, quint16 _hostPort);
void getDataBack(const myObjectPtr data);
protected Q_SLOTS:
void dataAvailable();
protected:
// TCP Data STream
QTcpSocket* myTcpConnection;
// QObject derived class for information about the thread
dataInfoObject* myInfo;
void myThreadInit();
public:
myThread();
~myThread();
void run();
};
...
myThread::myThread()
{
myInfo = NULL;
myTcpConnection = NULL;
}
void myThread::run()
{
myThreadInit();
exec();
}
void myThread::myThreadInit()
{
myInfo = new dataInfoObject(this);
// connect internal stuff
}
void myThread::setServer(const QString& _hostAddress, quint16 _hostPort)
{
if (myTcpConnection == NULL)
{
myTcpConnection = new QTcpSocket(this);
if (myTcpConnection != NULL)
{
// connect stuff up here - like readyRead(), connected(), etc.
}
}
if (myTcpConnection != NULL)
{
myTcpConnection->connectToHost(_hostAddress,_port);
}
}
...


The derived thread's run() simply calls its exec(), which to my understanding is needed to use the thread's event queue and signals/slots.

The main thread creates a widget (which manages the display of the data), which creates the thread as part of its construction. After the thread is created, it connects several signals/slots for the two parts to communicate and then calls the thread's start.




class myWidget : public QWidget
{
Q_OBJECT
...
private Q_SLOTS:
void getData(const myObjectPtr data);
Q_SIGNALS:
void sendDataBack(const myObjectPtr data);
void setThreadInfo(const dataInfoObject& _objectInfo);
private:
myThread workerThread;
void createThread();
protected:
...
...
public:
myWidget(QWidget* parent=NULL);
};
...
myWidget::myWidget(QWidget* parent) : QWidget(parent)
{
workerThread = NULL;
...
createThread();
}
...
void myWidget::createThread()
{
if (workerThread == NULL)
{
workerThread = new myThread(this);
}
if (workerThread != NULL)
{
// connect stuff up - connection type not specified
connect(this,SIGNAL(setThreadInfo(const dataInfoObject&)),workerThread,SLOT(setInfo(const dataInfoObject&)));
...
workerThread->start();
Q_EMIT setThreadInfo(threadInfo);
}
}
...


I just implemented the QThread in the last couple days; however, I discovered today that all the signal/slots are being handled in the main thread context, not the child thread's context. Thus the main thread is still chewing up all the processing time.

All the data getting passed over the signals/slots is either native to Qt, native C++ (e.g. bool, unsigned int, etc.), or it's been registered with the MetaObject system (via Q_DECLARE_METATYPE() and qRegisterMetaType()) so that it can be copied by Qt's event system.

I don't have any locks (yet) as I have not yet seen a need for them - the data is either in one context or the other, not both. I have also tried using the moveToThread() on the data object before emitting the signal with the object.



...
dataObject->(QApplication::instance()->thread());
Q_EMIT sendData(dataObject);
...


One thing I have found is that when I initialize the data in the thread in myThreadInit() - I will get the error:

QObject: Cannot create children for a parent that is in a different thread.

if I provide a parent to the object even though it is creating it from within the thread's context.



// Generates: QObject: Cannot create children for a parent that is in a different thread.
myInfo = new dataInfoObject(this);

// doesn't generate: QObject: Cannot create children for a parent that is in a different thread.
myInfo = new dataInfoObject();


What am I doing wrong? What am I misunderstanding about QThreads?

BadKnees
22nd March 2009, 14:20
I think your objects are not created in the right thread. Here's how i do it:
Simplyfied. What happens in run() happens in a new thread. And connection type is auto, which will fall back to queued connection.
header:


#ifndef THREAD_HH
#define THREAD_HH 1

#include <QtCore/QThread>

#include <Client.hh>

#include "Package.hh"

class Thread:public QThread{
Q_OBJECT

public:
Thread();
~Thread();

protected:
void run();

signals:
void relayPackageToClient(Package&);
void relayPackageToGui(Package&);
};

#endif //THREAD_HH

Source:


#include "Settings.hh"
#include "Thread.hh"

Thread::Thread()
{

}

Thread::~Thread()
{

}

void Thread::run()
{
qRegisterMetaType<Package>("Package&");

Settings *settings = Settings::instance();

Client *client = new Client(false);

client->connectToHost(settings->value("Host", "localhost").toString(),
settings->value("Port", "5544").toInt(),
settings->value("Username").toString(),
settings->value("Password").toString());

QObject::connect(this, SIGNAL(relayPackageToClient(Package&)),
client, SLOT(sendPackage(Package&)));
QObject::connect(client, SIGNAL(incomingPackage(Package&)),
this, SIGNAL(relayPackageToGui(Package&)));

exec();

delete client;
}

TemporalBeing
23rd March 2009, 20:46
Ok. This example really helped - I did rework it so that the thread now creates an object on the heap, and sets up a series of signals to that object. This works very well, and it does indeed run in the thread's context.

So essentially:



class myThread : public QThread
{
...
Q_SIGNALS:
void setInfo(const dataInfoObject& _objectInfo);
void setServer(const QString& _hostAddress, quint16 _hostPort);
void getDataBack(const myObjectPtr data);
void dataAvailable();
....
protected:
myThreadInstanceObject* myInstance;
...
public:
myThread();
~myThread();
void run();
};
...
void myThread::run()
{
if (myInstance == NULL)
{
myInstance = new myThreadInstanceObject;
if (myInstance != NULL)
{
// connect myThread signals to myInstance slots
...
// connect myInstance signals to myThread signals
exec();
delete myInstance;
myInstance = NULL;
}
}
}


This works, and I'm getting about the same performance as I did before when receiving one data stream - the memory peaks a little higher though, and overall CPU usage (on a dual core) goes to ~66% (~44% main thread, ~20% worker thread). I don't know why the main thread is still taking so much CPU though - it's primarily just drawing in the paint events.

I played with moveToThread() a little, and it seems to be detrimental to performance in my scenario as it increased kernel space usage.

My memory issue still persists a little though - but namely when I'm running several data feeds (4) simultaneously. Perhaps the forum can enlighten me as to whether I should really be concerned...

My biggest concern is that I have a data object I send to the main thread. It gets used and later replaced; before it is replaced it is sent back to the worker thread that generated it. Only the most recent object is kept. However, when I stop receiving data (or disconnect the TCP connection) it the memory consumption doesn't go back down to pre-connection/pre-run levels (~7MB). Normally this would indicate a memory leak; though it does level off at ~46MB for a single connection when I'm creating all the data - so that would seem to indicate things are getting cleaned up. Is this just the behavior of using QLIst and QByteArray? If so, why would it then not level off (even if at 46N MB) with multiples (where N is the number of service threads).

Is there any way I can ensure that the event loop gets enough time to clean up data?