[Qt-interest] QHttp (and propably event loop) on OS X (Qt 4.4.3)

sebastian richter richter at fhtw-berlin.de
Wed Mar 4 12:30:06 CET 2009


I have extracted something like a skeleton for my approach, consisting 
of the two classes dispatcherThread and loaderThread.
Please note that this will not compile, but it's the way i'm dealing 
with the threaded QHttp's...
I have put comments into the code, so hopefully everything is clear...
Note that the comments beginning with OSX are the parts of the code 
which behave different on OS X and which do not work as expected...

**********************
// the loader thread
class loaderThread;
// the data to be loaded by loaderThreads
class loadData;

// the dispatcher
class dispatcherThread : public QThread
{
Q_OBJECT
public:
dispatcherThread();
~dispatcherThread();
// the number of loaderThreads to be used
void setNumThreads(unsigned num) {m_uNumThreads=num;}
// the number of overall queries
void setNumQueries(unsigned num) {m_uNumQueries=num;}
// the number of queries per loader thread
void setNumQueriesPerThread(unsigned num) {m_uNumQueriesPerThread=num;}
// the back ptr to store the loadData
void setResults(QQueue<loadData>* pResults) {m_pResults=pResults;}
// the query a.k.a keyword for the real world code
void setQuery(const QString& query) {m_Query=query;}
// error handling
bool hasHttpError() const {return m_bHttpError;}
const QString& httpErrorString() const {return m_HttpErrorString;}
protected slots:
// called when one of the loaderThreads has finished
void childFinished();
protected:
virtual void run();
QVector<loaderThread*> m_QueryThreads;
QQueue<loadData>* m_pResults;
unsigned m_uNumThreads;
unsigned m_uNumQueries;
unsigned m_uNumQueriesPerThread;
QMutex m_Mutex;
QString m_Query;
unsigned m_uStart;
bool m_bHttpError;
QString m_HttpErrorString;
};


// define this to get console message and further slots/signals
#define DEBUG_LOAD_THREAD

// the loader thread
class loaderThread : public QThread
{
Q_OBJECT
public:
loaderThread() {;}
~loaderThread();
// the start arg. the real world code loads the xml results
// [m_uStart,m_uStart+m_uNumQueries[ from the yahoo image serach api
// concerning the query m_Query
// the dispatcherThread increments the start arg in childFinished()
void setStart(unsigned start) {m_uStart=start;}
// the query
void setQuery(const QString& query) {m_Query=query;}
// the result back ptr
void setResults(QQueue<loadData>* pResults) {m_pResults=pResults;}
// the mutex to lock on (a dispatxcherThread embedded member)
void setMutex(QMutex* pMutex) {m_pMutex=pMutex;}
// the number of queries
void setNumQueries(unsigned num) {m_uNumQueries=num;}
// the timeOut is given in milliseconds (0 means no timer at all)
void setTimeOut(unsigned msecs) {m_uTimeOut=msecs;}
// error handling
QHttp::Error httpError() const {return m_HttpError;}
bool hasHttpError() const {return m_HttpError!=QHttp::NoError;}
QString httpErrorString() const {return m_HttpErrorString;}
// the id. the dispatcher thread identifies the loaderThreads
// in childFinished() by this id
void setId(unsigned id) {m_Id=id;}
protected slots:
void isDone(bool bError);
void timedOut(void);
#ifdef DEBUG_LOADER_THREAD
void stateChanged(int state);
void requestStarted(int id);
void requestFinished(int id, bool b);
#endif
protected:
virtual void run();
unsigned m_uStart;
QString m_Query;
QQueue<loadData>* m_pResults;
QMutex* m_pMutex;
unsigned m_uNumQueries;
bool m_bError;
unsigned m_Id;
unsigned m_uTimeOut;
bool m_bTimedOut;
QHttp::Error m_HttpError;
QString m_HttpErrorString;
#ifdef DEBUG_LOADER_THREAD
int m_SetHostId;
int m_GetId;
#endif
};

dispatcherThread::dispatcherThread()
{
m_uNumThreads=2;
// note this is essential...
moveToThread(this);
}

dispatcherThread::~dispatcherThread()
{
unsigned i;
for(i=0;i<m_QueryThreads.size();++i){
delete m_QueryThreads[i];
}

m_QueryThreads.clear();
}

void dispatcherThread::run()
{
if(!m_uNumThreads||!m_pResults||!m_uNumQueries||!m_uNumQueriesPerThread||m_Query.isEmpty()){
assert(false);
return;
}

m_bHttpError=false;
m_HttpErrorString.clear();

// don't have more threads than queries
unsigned numThreads=qMin(m_uNumThreads,m_uNumQueries);

// create the threads we need
while(m_QueryThreads.size()<numThreads){
loaderThread* p=new loaderThread;
p->setQuery(m_Query);
p->setResults(m_pResults);
p->setMutex(&m_Mutex);
p->setNumQueries(m_uNumQueriesPerThread);
p->setTimeOut(5000);
m_QueryThreads.append(p);
}

assert(m_QueryThreads.size()==numThreads);

m_uStart=1;

// set the id's and the first run args, then start the threads
for(unsigned i=0;i<numThreads;++i){
m_QueryThreads[i]->setId(i);
connect(m_QueryThreads[i],SIGNAL(finished()),this,SLOT(childFinished()),Qt::QueuedConnection);
m_QueryThreads[i]->setStart(m_uStart);
m_uStart+=m_uNumQueriesPerThread;
m_QueryThreads[i]->start();
}

// run the event loop
exec();

// we may have still child threads running, wait for them
bool bDone=false;
while(!bDone){
bDone=true;
for(unsigned i=0;i<numThreads;++i){
if(!m_QueryThreads[i]->isFinished()){
bDone=false;
//break;
}
}
}

}

void dispatcherThread::childFinished()
{
// mutual exclusive...
m_Mutex.lock();

bool bExit=false;

// get the first loader which is finished
unsigned i,id;
bool bFound=false;
bool bActive=false;
for(i=0;i<m_QueryThreads.size();++i){
if(m_QueryThreads[i]->isFinished()){
if(!bFound){
id=i;
bFound=true;
}
}
else
bActive=true;
}

if(!bFound){
// note i don't know why, but this really happens...
// assert(false);
m_Mutex.unlock();
return;
}

if(m_QueryThreads[id]->hasHttpError()){
// one of the threads has an error. it is most likely that
// other threads will have an error also. so don't start any new threads 
any more.
// we may build in better handling later, i.e. based on the QHttp::Error...
m_bHttpError=true;
m_HttpErrorString=m_QueryThreads[id]->httpErrorString();
qDebug() << m_HttpErrorString;
}

unsigned sz=m_pResults->size();

// stop if we have enough results
if(sz>=m_uNumQueries)
bExit=true;
else if(!m_bHttpError){
m_QueryThreads[id]->setStart(m_uStart);
// increment start arg
m_uStart+=m_uNumQueriesPerThread;
m_QueryThreads[id]->start();
}
m_Mutex.unlock();

if(m_bHttpError)
bExit=true;

if(bExit&&!bActive)
exit(0);
}


#ifdef DEBUG_LOADER_THREAD
#include <QtDebug>
#endif

loaderThread::~loaderThread()
{
assert(isFinished());
}

void loaderThread::run()
{
m_bError=m_bTimedOut=false;

if(!m_pMutex||!m_uNumQueries||!m_pResults||m_Query.isEmpty()){
return;
}

// the timer for the timeout
QTimer timer;
timer.setSingleShot(true);
connect(&timer,SIGNAL(timeout()),this,SLOT(timedOut()));

// the QHttp instance and the buffer to load into
QBuffer buf;
QHttp http;

// just for debugging
#ifdef DEBUG_LOADER_THREAD
connect(&http,SIGNAL(stateChanged(int)),this,SLOT(stateChanged(int)));
connect(&http,SIGNAL(requestStarted(int)),this,SLOT(requestStarted(int)));
connect(&http,SIGNAL(requestFinished(int,bool)),this,SLOT(requestFinished(int,bool)));
#endif

// the QHttp::setHost() and QHttp::get() requests...
#ifdef DEBUG_LOADER_THREAD
m_SetHostId=
#endif
http.setHost("the_yahoo_imagesearch_api_host");

#ifdef DEBUG_LOADER_THREAD
qDebug() << "run() : setHostId : " << m_SetHostId;
#endif
connect(&http,SIGNAL(done(bool)),this,SLOT(isDone(bool)));
if(m_uTimeOut)
timer.start(m_uTimeOut);

#ifdef DEBUG_LOADER_THREAD
m_GetId=
#endif
http.get("some_string_args"+m_Query.toUtf8()+"&results="+QString::number(m_uNumQueries)+"&start="+QString::number(m_uStart),&buf);

#ifdef DEBUG_LOADER_THREAD
qDebug() << "run() : getId : " << m_GetId;
#endif

// run the event loop
// this returns when the request is finished, either with or w/o error, 
or when the timer timed out
exec();

http.disconnect(this);
timer.disconnect(this);

// OSX: this happens always...
if(m_bTimedOut)
http.abort();

timer.stop();

m_HttpError=http.error();
m_HttpErrorString=http.errorString();

if(m_bError){
return;
}

// if there is no error, read the data in buf into m_pResults
// this is out of scope of this example code...
buf.close();
return;
}

// OSX: this is never called
void loaderThread::isDone(bool bError)
{
m_bError=bError;
m_bError?exit(1):exit(0);
}

// OSX: this is always called
void loaderThread::timedOut()
{
m_bTimedOut=true;
exit(1);
}

// OSX: these are never called
// so even requestStarted() for the first QHttp request (setHost()) is 
not emitted...
#ifdef DEBUG_LOADER_THREAD
void loaderThread::stateChanged(int state)
{
qDebug() << "stateChanged() : state : " << state;
}

void loaderThread::requestStarted(int id)
{
qDebug() << "requestStarted() : id :" << id;
}

void loaderThread::requestFinished(int id,bool b)
{
qDebug() << "requestFinished() : id :" << id;
qDebug() << "requestFinished() : bool :" << b;
}
#endif

**********************

Regards,

Sebastian



More information about the Qt-interest-old mailing list