简单的答案:你不应该阻塞异步的、运行到完成的代码——a 中的每个事件处理程序和插槽实现QObject
都应该尽快完成它的工作并返回。它不应该做任何忙碌的等待或睡眠。有关这条线的更多咆哮,请参阅 Miro Samek 的我讨厌 RTOSes。
对于从上面遵循的更好的实现,请参阅此答案。下面的宏诡计最好留给被 C 卡住的可怜的灵魂。
我附上了一个示例,说明如何以正确的方式进行操作,至少从代码功能的角度来看。如果你想要一个真正的实现,看看Boost 的 stackless coroutines。
宏诡计是句法糖——它使技术更可口(Boost 比我在下面做的更好)。无论您是使用宏还是显式写出方法,都取决于您。语法并不是所谓的“正确方法”。我不是唯一一个使用这种预处理器诡计的人。缺少对嵌套函数调用的支持,以及在QObject
. 该示例仅显示了一个“线程”的代码,并且仅显示了一级异步函数调用。Stackless Python得出了合乎逻辑的结论。
如果您以异步方式编写它,您将在所有代码中看到这种模式。宏是语法糖,SLEEP
有助于使代码更易于理解。如果没有 C++ 中的 hacky 宏,就没有真正干净的方法来编写它,其中语法不会过于霸道。即使从 C++11 开始,该语言也没有对 yield 的内置支持。请参阅为什么没有将 yield 添加到 C++0x?.
这是真正的非阻塞代码,您会看到周期性计时器事件在您“睡着”时触发。请注意,这种协作式多任务处理的开销比操作系统完成的线程/进程切换要低得多。以这种方式编写 16 位 Windows 应用程序代码是有原因的:它的性能相当好,即使在微不足道的硬件上也是如此。
请注意,此代码不需要 a ,实际上也不需要 a ,尽管如果您将对象移动到高优先级线程,则延迟的传播会更小。QThread
QThread
Qt 计时器实现足够聪明,可以减少 Windows 上的计时器滴答周期,如果周期“短”的话。您可以使用我在下面显示的特定于平台的代码,但不建议这样做。在 Qt 5 上,您只需启动一个Qt::PreciseTimer
计时器。请注意,在 Windows 8 之前的系统上,您需要权衡功耗和稍高的内核开销来换取性能。Windows 8、OS X (xnu) 和现代 Linux 是无滴答的,不会遭受这种性能下降的影响。
我应该承认使用 ## 和 __LINE__ 创建 C 宏(与定位宏的令牌连接)中明确的预处理器滥用方向。
与宏类似SLEEP()
,您也可以实现GOTO()
宏,以允许您拥有以更易于遵循的阻塞代码样式编写的简单有限状态机,但在幕后是异步的。您可以使用宏来实现在状态进入和退出等时执行的操作,但代码看起来完全像一个直接编码的阻塞式函数ENTER()
。LEAVE()
我发现它比没有任何语法糖衣的代码效率高,而且更容易理解。YMMV。最后,您将有一些东西正在通往 UML 状态图的路上,但开销(运行时和代码文本方面)比QStateMachine-based
实现要少。
下面是输出,星号是周期性的计时器滴答声。
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"