Unsolved Optimal way to establish large number of QTimers
-
That's great, thank you @JKSH !
I'll do some testing on this over the coming couple of days. The only issue I can foresee is in the integer requirement of the pollPeriod (1000/pollFrequency). I can enforce this by restricting the user-selectable poll frequencies available, but want to allow a highly flexible selection if possible. As such, I might need to sacrifice absolute frequency precision by rounding up/down to the nearest integer. Unless there's a comparable method which uses floats...
-
You're welcome!
@jars121 said in Optimal way to establish large number of QTimers:
The only issue I can foresee is in the integer requirement of the pollPeriod (1000/pollFrequency).
I guess if you don't mind a little bit of jitter, you could code in in such a way that the loop sleeps for, say, 33 ms most of the time but 34 ms sometimes if you want pollFrequency=30 Hz (so pollPeriod = 33.333... ms).
Someone mentioned this at https://www.mail-archive.com/interest@qt-project.org/msg28783.html -- read through the whole thread.
I can enforce this by restricting the user-selectable poll frequencies available, but want to allow a highly flexible selection if possible. As such, I might need to sacrifice absolute frequency precision by rounding up/down to the nearest integer.
You could ask the user to specify period instead of frequency. That gives you maximum flexibility without having to worry about rounding errors.
Unless there's a comparable method which uses floats...
1000/30 cannot be represented exactly as a floating-point number. The value will be close to, but not equal to 33.333...
-
I still don't understand why you'd want to dedicate a whole thread only to do this endless loop, as I mentioned, polling (which is the worst way you can get data from somewhere) needs no high precision or stability on the interval. If it does, then you need to use an API which actually provides asynchronous data transfer, and not polling. If your API doesn't have this, well then you're somewhat stuck ... and I know the feeling, I hope you don't think I'm just criticizing polling just for kicks.
Furthermore
sleep
and its variants can't be interrupted, which is rather notorious. So how do you exit that endless loop? Killing the thread is not an option, so you must wait for thesleep
to exit and only then handle the quitting. And if your application is hanging there waiting for thesleep
to finish and the OS is doing its shutdown sequence, the latter might just decide to kill the process anyway, as it's not responding ... quite the vicious circle ...If you really, really need to sleep in a thread, you should do it cleverly, so you can at any time break the sleep and do shutdown/cleanup/w/e. To that end you can use
QSemaphore
off-label, consider the following:// ... time calculations and such QSemaphore semaphore; while (true) { if (semaphore.tryAcquire(1, timeToSleep)) break; // Oh-oh we were woken up in the middle of our nap to break the loop and quit (!) // ... timeToSleep milliseconds have passed }
Now you can break the sleep at any one time from the main thread by calling
semaphore.release()
. If you do this with a signal-slot invocation, then you need to make sure you useQt::DirectConnection
.My original advice still stands though - use a
QTimer
. -
Following @kshegunov 's post, I've implemented a QTimer approach, using a dedicated QObject moved to separate threads, as per @ambershark 's post above.
One question, in ambershark's example, the SLOT() is within the handler class, and simply prints the threadId after each timeout. I've got this method working in my application. How can I use a SLOT() within my main class (from where the QThread and QTimer are established)? My understanding is that the connect call can't see the main class SLOTS, as they're external to the handler QObject class.
In that case, would I still use a SLOT() in the handler class, and simply redirect to a public function in the main class?
-
@jars121 If you're calling connect() in your main class then you can use slots from main class there as they are part of that class. Or do I misunderstand your question?
-
@jsulm said in Optimal way to establish large number of QTimers:
@jars121 If you're calling connect() in your main class then you can use slots from main class there as they are part of that class. Or do I misunderstand your question?
You understand the question perfectly. That was my understanding as well. I have a public slot defined in my main header:
//main.h public slots: void analogTimeout(); void digitalTimeout();
In my main.cpp file, I then establish the QThreads() and TimerHandlers() as per @ambershark 's earlier post:
//main.cpp QThread* analogThread = new QThread(); TimerHandler *analogTimerHandler = new TimerHandler(); analogTimerHandler->moveToThread(analogThread); QTimer *analogTimer = new QTimer(); analogTimer->setInterval(1000); connect(analogTimer, SIGNAL(timeout()), analogTimerHandler, SLOT(analogTimeout())); analogThread->start(); analogInputTimer->start();
This results in the following error:
QObject::connect: No such slot TimerHandler::analogTimeout() in ../app/main.cpp:100
The above error is what sparked my question. The context for the QObject::connect appears wrong. I'm calling connect() from within main.cpp, but the slot context is within the TimerHandler class.
I imagine I'm overlooking something simple...
-
@jars121 analogTimeout() is a slot in main class not in TimerHandler, right?
-
@jars121 said in Optimal way to establish large number of QTimers:
The context for the QObject::connect appears wrong. I'm calling connect() from within main.cpp, but the slot context is within the TimerHandler class.
This is wrong. It doesn't matter where you call connect. The slot you pass to it belongs to the object you pass to it as well. That means analogTimeout() must be a slot in TimerHandler, not in main.
// SIGNAL(timeout()) - belongs to analogTimer // SLOT(analogTimeout()) - belongs to analogTimerHandler connect(analogTimer, SIGNAL(timeout()), analogTimerHandler, SLOT(analogTimeout()));
If you want to connect your main then do
connect(analogTimer, SIGNAL(timeout()), this, SLOT(analogTimeout()));
Or if slot should really be part of AnalogTimerHandler then move it there.
-
Thank you @jsulm ! You've cleared up my confusion perfectly; I was overlooking the class reference in connect() entirely.
The timeout function now runs as required, but it doesn't appear to have solved the threading issue. I have a qDebug() on the main thread, as well as on each of the two newly created threads (one for analogInputs and one for digitalInputs), as follows:
//main.cpp qDebug() << "Main thread ID: " << QThread::currentThread(); QThread* analogThread = new QThread(); TimerHandler *analogTimerHandler = new TimerHandler(); analogTimerHandler->moveToThread(analogThread); QTimer *analogTimer = new QTimer(); analogTimer->setInterval(1000); connect(analogTimer, SIGNAL(timeout()), this, SLOT(analogTimeout())); analogThread->start(); analogTimer->start(); QThread* digitalThread = new QThread(); TimerHandler *digitalTimerHandler = new TimerHandler(); digitalTimerHandler->moveToThread(digitalThread); QTimer *digitalTimer = new QTimer(); digitalTimer->setInterval(1000); connect(digitalTimer, SIGNAL(timeout()), this, SLOT(digitalTimeout())); digitalThread->start(); digitalTimer->start(); } void Main::analogTimeout() { qDebug() << "Analog: " << QThread::currentThread(); } void Main::digitalTimeout() { qDebug() << "Digital: " << QThread::currentThread();
As I mentioned above, the above works (as in analogTimeout() and digitalTimeout() are called every second), but the qDebug() prints show that the two timeout threads and the main thread (printed earlier) are all running on the same thread. My understanding was that QThread* analogThread = new QThread() and QThread* digitalThread = new QThread() would he put the respective TimerHandlers on threads separate to the main thread? Why are they all on the same (main) thread?
-
@jars121 You move analogTimerHandler to a thread, but you don't use it at all?!
// Both analogTimer and this are in same thread connect(analogTimer, SIGNAL(timeout()), this, SLOT(analogTimeout()));
-
Good point.
The analogTimerHandler only serves a purpose it seems when the connect() slot is a function within the TimerHandler class, otherwise I move the instance of the class to the thread, and don't use it.
As such, should I create the QTimer within the TimerHandler class, and start the QTimer when the QThread is started?
-
@jars121 said in Optimal way to establish large number of QTimers:
As such, should I create the QTimer within the TimerHandler class, and start the QTimer when the QThread is started?
Either move the timer after you create it, like you do for the worker object, or create the timer in the worker object (give the worker object as parent!) and then just move the worker object. It's a matter of convenience and up to you.