QElapsedTimer anomalous behavior
-
Hello i am quite new to Qt, using it since few months, and developing a multi-threaded app using QThread i have run into an anomalous behaviour of the return value of the function elapsed() from the class QElapsedTimer, sometimes the return value jump to a very high value, something close to the 32bit limit, the problem appears only if i have different instances of the class in different threads where every single instance is used privately only by the owner thread.
The source code of the app where i have discovered this behaviour is too heavy to put here so i have made a simple program to exhibit the problem, in the test code i have made on purpose a gui timer that every 1 second enter in a blocking loop testing a QElapsedTimer elapsed() for about 500ms before exiting the gui timer slot, this help to produce the problem quickly while some threads are calling elapsed() on different private object.
The problem appears only on win plattform, compiling the same code on mac work fine."Test video":http://www.youtube.com/watch?v=5xHI82ClicY
I have temporary solved the problem using QTime instead of QElapsedTimer, where QTime seems to work fine in multiple threads, but QTime seems more slow and have the drawback of being altered by host system clock changes.
mainwindows.h
@#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QtGui>namespace Ui {
class MainWindow;
}class TH : public QThread
{
Q_OBJECT
public:
explicit TH(void);
virtual ~TH();void Start (int index); void Engine(void); virtual void run(void);
signals:
void ThEvent(int index, QString message);private:
QAtomicInt main_loop;
QAtomicInt my_index;
};#define MAX 10
class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();private:
Ui::MainWindow *ui;
TH th[MAX];
QPointer<QTimer> timer;private slots:
void on_pushButton_clicked();
void ThEvent(int index, QString message);
void OnTimer(void);
};#endif // MAINWINDOW_H
@mainwindows.cpp
@#include "mainwindow.h"
#include "ui_mainwindow.h"TH::TH(void)
{
main_loop=0;
moveToThread(this);
}TH::~TH()
{
main_loop=0;
if(wait(3000)==false)
{
terminate();
}
}void TH::Start(int index)
{
main_loop=1;
my_index=index;
start();
}void TH::Engine(void)
{
}void TH::run(void)
{
QEventLoop event_loop;
QElapsedTimer timer;
unsigned int c;
qint64 elapsed;
qint64 min;
qint64 max;
QString str_tmp;timer.start(); c=0; min=100; max=100; while(main_loop) { event_loop.processEvents(QEventLoop::AllEvents); Engine(); elapsed=timer.elapsed(); if(elapsed>=100) { timer.start(); if(elapsed<min) min=elapsed; if(elapsed>max) max=elapsed; str_tmp="TH_ID="; str_tmp+=QString::number((quint64)QThread::currentThreadId()); str_tmp+=" ELAPSED="; str_tmp+=QString::number(elapsed); str_tmp+=" MIN="; str_tmp+=QString::number(min); str_tmp+=" MAX="; str_tmp+=QString::number(max); str_tmp+=" COUNTER="; str_tmp+=QString::number(c++); emit ThEvent(my_index,str_tmp); } msleep(1); }
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
unsigned int i;ui->setupUi(this); for(i=0;i<MAX;i++) { connect(&(th[i]),SIGNAL(ThEvent(int,QString)),this,SLOT(ThEvent(int,QString)),Qt::QueuedConnection); } timer=new QTimer(this); if(timer) { connect(timer,SIGNAL(timeout()),this,SLOT(OnTimer())); timer->start(1000); }
}
MainWindow::~MainWindow()
{
delete ui;
}void MainWindow::on_pushButton_clicked()
{
unsigned int i;for(i=0;i<MAX;i++) { th[i].Start(i); }
}
void MainWindow::ThEvent(int index, QString message)
{
if(index>=ui->list_events->count())
{
ui->list_events->addItem(message);
}
else
{
QListWidgetItem *item;item=ui->list_events->item(index); if(item) { item->setData(Qt::DisplayRole,"APP_TH_ID="+QString::number((quint64)QThread::currentThreadId())+" "+message); } }
}
void MainWindow::OnTimer(void)
{
QElapsedTimer time;time.start(); while(time.elapsed()<500) { }
}
@Anyone can help me to fix this problem?
I am missing something in my code?Thanks.
-
I think you should report this as a bug.
-
Before you do, try using the thread in a proper way, without using
@moveToThread(this);@
It's considered bad style, and it's rather odd having the thread interface living in it's own thread. -
Good point.
-
For more information: "You're doing it wrong...":http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/.
-
...and of course "Peppe's":http://developer.qt.nokia.com/member/5319 very good wiki article on "Threads, Events and QObjects":http://developer.qt.nokia.com/wiki/Threads_Events_QObjects right here on DevNet.
-
Thank you for the help to everyone!
I have read the articles, they are very helpfull about how to use QThread class, so i have changed the test source code to see if my problem could be fixed, but it seems to show the same behavior.
Let me post the new test source code to see if i have got how to code correctly threads in Qt, this is meant to test the QElapsedTimer class on different threads and the fact i have made idle timers in the worker classes that use all the cpu time is only meant for the purpose to "stress" the .elapsed() function to reproduce the problem quickly, in the real application .elapsed() function is called more slowly but the problem is the same, it just appears less frequently.MyWorker.h
@class MyWorker : public QObject
{
Q_OBJECT
public:
explicit MyWorker(void);
virtual ~MyWorker();signals:
void ThEvent(int index, QString message);private slots:
void OnIdle(void);private:
static int index_counter;
int my_index;
QElapsedTimer timer;
qint64 elapsed;
qint64 elapsed_min;
qint64 elapsed_max;
QString str_tmp;
unsigned int counter;
QPointer<QTimer> idle_timer;
};
@MyThread.h
@class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(void);
virtual ~MyThread();virtual void run(void);
signals:
void ThEvent(int index, QString message);
};@MainWindow.h
@class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
explicit MainWindow(QWidget *parent = 0);
virtual ~MainWindow();private:
Ui::MainWindow *ui;
MyThread thread[MAX];private slots:
void on_pushButton_clicked();
void ThEvent(int index, QString message);
};@MyWorker.cpp
@int MyWorker::index_counter=0;MyWorker::MyWorker(void)
:QObject()
{
my_index=index_counter++;
timer.start();
elapsed_min=100;
elapsed_max=100;
counter=0;
idle_timer=new QTimer(this);
if(idle_timer)
{
connect(idle_timer,SIGNAL(timeout()),this,SLOT(OnIdle()));
idle_timer->start(0);
}
}MyWorker::~MyWorker()
{
}void MyWorker::OnIdle(void)
{
elapsed=timer.elapsed();
if(elapsed>=100)
{
timer.start();
if(elapsed<elapsed_min) elapsed_min=elapsed;
if(elapsed>elapsed_max) elapsed_max=elapsed;
str_tmp="TH_ID=";
str_tmp+=QString::number((quint64)QThread::currentThreadId());
str_tmp+=" ELAPSED=";
str_tmp+=QString::number(elapsed);
str_tmp+=" MIN=";
str_tmp+=QString::number(elapsed_min);
str_tmp+=" MAX=";
str_tmp+=QString::number(elapsed_max);
str_tmp+=" COUNTER=";
str_tmp+=QString::number(counter++);
emit ThEvent(my_index,str_tmp);
}
}
@MyThread.cpp
@MyThread::MyThread(void)
:QThread(0)
{
}MyThread::~MyThread()
{
quit();
wait();
}void MyThread::run(void)
{
MyWorker *worker;worker=new MyWorker(); if(worker) { worker->moveToThread(this); connect(worker,SIGNAL(ThEvent(int,QString)),this,SIGNAL(ThEvent(int,QString))); exec(); delete worker; }
}@
MainWindow.cpp
@MainWindow::MainWindow(QWidget *parent)
:QMainWindow(parent),
ui(new Ui::MainWindow)
{
unsigned int i;ui->setupUi(this); for(i=0;i<MAX;i++) { connect(&(thread[i]),SIGNAL(ThEvent(int,QString)),this,SLOT(ThEvent(int,QString))); }
}
MainWindow::~MainWindow()
{
delete ui;
}void MainWindow::on_pushButton_clicked()
{
unsigned int i;for(i=0;i<MAX;i++) { thread[i].start(); }
}
void MainWindow::ThEvent(int index, QString message)
{
if(index>=ui->list_events->count())
{
ui->list_events->addItem(message);
}
else
{
QListWidgetItem *item;item=ui->list_events->item(index); if(item) { item->setData(Qt::DisplayRole,"APP_TH_ID="+QString::number((quint64)QThread::currentThreadId())+" "+message); } }
}@
I have tried to alloc the worker classes in the main windows together with QThread classes and than using the way:
worker.moveToThread(&thread);
but it seems to work bad as i create the idle_timer in the ctor of the worker where i cannot pass the parent because it will later move to a thread, so i have found better the way to create (alloc) the worker class inside the run() of th thread (so from the thread executing path), connecting the signals and than call the exec() to start the thread event loop and than delete the worker object before leaving the run() function so deleting it from the thread executing path, i hope this is the correct way to code it.