The simple answer: you're not supposed to block in asynchronous, run-to-completion code -- every event handler and slot implementation in a QObject
is supposed to do its job and return, as soon as possible. It's not supposed to do any sort of busy waiting or sleeping. For more ranting along this line, see Miro Samek's I hate RTOSes.
For a much better implementation that follows from the above, see this answer instead. Macro trickery that follows below is best left to the poor souls stuck with C.
I've attached an example of how to do it the right way at least from the point of view of what the code does. If you want a real implementation, look no farther than Boost's stackless coroutines.
The macro trickery is syntactic sugar - it makes the technique more palatable (Boost does it better than I do below). Whether you use macros or write out the methods explicitly, is up to you. The syntax is not what is claimed to be the "right way" of doing it. I'm not the only one to use such preprocessor trickery. Missing is support nested function calls, and multiple "threads" of run-to-completion execution within a QObject
. The example shows code for only one "thread" and only one level of async function calls. Stackless Python takes this to the logical conclusion.
You'll see this pattern in all of your code if you write it in an asynchronous way. The SLEEP
macro is syntax sugar to help make the code easier to follow. There's no truly clean way to write it without a hacky macro in C++ where the syntax wouldn't be overbearing. Even as of C++11, the language has no built-in support for yield. See Why wasn't yield added to C++0x?.
This is truly non-blocking code, you'll see that the periodic timer event fires while you're "asleep". Do note that this cooperative multitasking has a much lower overhead than thread/process switches done by the OS. There's a reason why 16 bit Windows application code was written this way: it performs quite well, even on meager hardware.
Note that this code does not need a QThread
, and in fact doesn't use a QThread
, although if you'd move the object to a high priority thread, the delays will have lower spread.
The Qt timer implementation is clever enough to decrease the timer tick period on Windows, if the period is "short". You can use the platform-specific code I show below, but it should be discouraged. On Qt 5, you'd simply start a Qt::PreciseTimer
timer. Do note that on pre-Windows 8 systems you're trading off power consumption and a slightly higher kernel overhead for performance here. Windows 8, OS X (xnu) and modern Linux are tickless and don't suffer from such performance degradation.
I should acknowledge the clear preprocessor abuse direction from Creating C macro with ## and __LINE__ (token concatenation with positioning macro).
Similarly to the SLEEP()
macro, you can also implement a GOTO()
macro, to allow you having simple finite state machines that are written in an easier-to-follow blocking code style, yet are asynchronous behind the scenes. You can have ENTER()
and LEAVE()
macros to implement actions to be done on state entry and exit, etc, yet the code can look entirely like a straight-coded blocking-style function. I've found it quite productive, and easier to follow than code that lacks any syntactic sugarcoating. YMMV. In the end, you would have something that's on the way to UML statecharts, but with less overhead (both runtime and code-text-wise) than QStateMachine-based
implementations.
Below is the output, the asterisks are periodic timer ticks.
doing something
*
*
*
*
*
*
*
*
*
*
slept, a=10
*
*
*
*
*
slept, a=20
*
*
slept, a=30
*
slept, a=40
#sleep.pro
QT += core
QT -= gui
TARGET = sleep
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
//main.cpp
#ifdef Q_WS_WIN
#include <windows.h>
#endif
#include <cstdio>
#include <QtCore/QTextStream>
#include <QtCore/QObject>
#include <QtCore/QBasicTimer>
#include <QtCore/QTimer>
#include <QtCore/QCoreApplication>
QTextStream out(stdout);
// this order is important
#define TOKENPASTE2(x,y) x ## y
#define TOKENPASTE(x,y) TOKENPASTE2(x,y)
#define SLEEP(ms) sleep(ms, &SLEEPCLASS::TOKENPASTE(fun, __LINE__)); } void TOKENPASTE(fun, __LINE__)() {
class Object : public QObject
{
Q_OBJECT
#define SLEEPCLASS Object // used by the SLEEP macro
public:
Object() {
QTimer::singleShot(0, this, SLOT(slot1()));
periodic.start(100);
connect(&periodic, SIGNAL(timeout()), SLOT(tick()));
}
protected slots:
void slot1() {
a = 10; // use member variables, not locals
out << "doing something" << endl;
sleep(1000, &Object::fun1);
}
void tick() {
out << "*" << endl;
}
protected:
void fun1() {
out << "slept, a=" << a << endl;
a = 20;
SLEEP(500);
out << "slept, a=" << a << endl;
a = 30;
SLEEP(250);
out << "slept, a=" << a << endl;
a = 40;
SLEEP(100);
out << "slept, a=" << a << endl;
qApp->exit();
}
private:
int a; // used in place of automatic variables
private:
void sleep(int ms, void (Object::*target)()) {
next = target;
timer.start(ms, this);
}
void timerEvent(QTimerEvent * ev)
{
if (ev->timerId() == timer.timerId()) {
timer.stop(); (this->*next)();
}
}
QTimer periodic;
QBasicTimer timer;
void (Object::* next)();
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object o1;
#ifdef Q_WS_WIN
timeBeginPeriod(1); // timers will be accurate to 1ms
#endif
return a.exec();
}
#include "main.moc"
sleep()
a protected member of theQThread
class. And so, when you write your thread as a slot hooked up to aQThread
's signal, you don't have direct access to it – Gujralsleep
function exist? Because sometimes, sleep is actually the correct solution... – Gujralsleep
is often the wrong thing to want to be doing in the first place. There's nothing fundamentally wrong with usingstd::thread
in a Qt app if you don't want/need the event-driven model and the crappy thread class that comes with it. – Lietmanvoid run()
. But the default way of doing things should be always by having asynchronous, run-to-completion code in slots in a QObject. See, for example, I Hate RTOSes by Miro Samek embeddedgurus.com/state-space/2010/04/i-hate-rtoses. Then, when benchmarking shows that the code you want to run is CPU starved (whether by the GUI thread, or the thread it's already in), you move it to a new thread. – Briar