当我在Qt 5中创建
QTimer
对象并使用start()
成员函数启动它时,创建了一个单独的线程来跟踪时间并调用timeout()
函数定期?
例如,
QTimer *timer = new QTimer;
timer->start(10);
connect(timer,SIGNAL(timeout()),someObject,SLOT(someFunction()));
此处,程序如何知道timeout()
何时发生?我认为它必须在一个单独的线程中运行,因为我不知道顺序程序如何跟踪时间并继续同时执行。但是,我无法在Qt文档或其他任何地方找到任何相关信息来确认这一点。
我已阅读the official documentation,而且有关StackOverflow的某些问题,例如this和this似乎非常相关,但我无法通过它们得到答案。
有人能解释QTimer
对象的工作机制吗?
在进一步搜索时,我发现this answer按照Bill提到
事件由操作系统异步传递,这就是为什么看起来还有其他事情发生的原因。有,但不在您的计划中。
这是否意味着操作系统处理timeout()
?是否有一些硬件能够跟踪时间并以适当的间隔发送中断?但如果是这种情况,由于许多计时器可以同时独立运行,每个计时器如何被单独跟踪?
机制是什么?
谢谢。
答案 0 :(得分:23)
当我在Qt 5中创建QTimer对象时,使用start()启动它 成员函数,是一个创建的单独线程,用于跟踪 time并定期调用timeout()函数?
没有;创建一个单独的线程将是昂贵的,并没有必要,所以这不是QTimer的实现方式。
这里,程序如何知道timeout()何时发生?
QTimer :: start()方法可以调用系统时间函数(例如gettimeofday()或类似函数)来查找(在几毫秒内)调用start()的时间。然后,它可以添加10毫秒(或您指定的任何值),现在它有一条记录,指示何时应该发出timeout()信号。
因此,有了这些信息,它会做些什么来确保这一点?
要知道的关键事实是QTimer超时信号发射仅在Qt程序在Qt的事件循环内执行时才有效。几乎每个Qt程序都会有这样的东西,通常在它的main()函数的底部附近:
QApplication app(argc, argv);
[...]
app.exec();
请注意,在典型的应用程序中,几乎所有应用程序的时间都将花在exec()调用中;也就是说,在应用程序退出之前,app.exec()调用将不会返回。
那么在程序运行时,exec()调用内部会发生什么?有了像Qt这样的大型复杂库,它必然会变得复杂,但是说它正在运行一个概念性的事件循环并不是太简单:
while(1)
{
SleepUntilThereIsSomethingToDo(); // not a real function name!
DoTheThingsThatNeedDoingNow(); // this is also a name I made up
if (timeToQuit) break;
}
因此,当您的应用程序处于空闲状态时,该进程将在SleepUntilThereIsSomethingToDo()调用内进入休眠状态,但只要事件到达需要处理(例如用户移动鼠标,或按键,或数据到达)在套接字或其它方面),SleepUntilThereIsSomethingToDo()将返回,然后将执行响应该事件的代码,从而产生适当的操作,例如小部件更新或调用timeout()信号。
那么SleepUntilThereIsSomethingToDo()如何知道何时醒来并返回?这将根据您运行的操作系统而有很大差异,因为不同的操作系统具有不同的API来处理此类事情,但实现此类功能的经典UNIX-y方式将是POSIX select()调用:
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
请注意,select()接受三个不同的fd_set参数,每个参数都可以指定多个文件描述符;通过将适当的fd_set对象传递给那些参数,可以使select()在您需要监视的一组文件描述符中的任何一个上唤醒I / O操作,以便程序可以处理I / O没有延迟。然而,对我们来说有趣的部分是最终参数,这是一个超时参数。特别是,你可以传入一个struct timeval
对象来说明select():“如果在这么多微秒之后没有发生I / O事件,那么你应该放弃并返回”。< / p>
结果证明非常有用,因为通过使用该参数,SleepUntilThereIsSomethingToDo()函数可以执行类似这样的操作(伪代码):
void SleepUntilThereIsSomethingToDo()
{
struct timeval now = gettimeofday(); // get the current time
struct timeval nextQTimerTime = [...]; // time at which we want to emit a timeout() signal, as was calculated earlier inside QTimer::start()
struct timeval maxSleepTimeInterval = (nextQTimerTime-now);
select([...], &maxSleepTimeInterval); // sleep until the appointed time (or until I/O arrives, whichever comes first)
}
void DoTheThingsThatNeedDoingNow()
{
// Is it time to emit the timeout() signal yet?
struct timeval now = gettimeofday();
if (now >= nextQTimerTime) emit timeout();
[... do any other stuff that might need doing as well ...]
}
希望这是有道理的,你可以看到事件循环如何使用select()的超时参数来允许它唤醒并在(大约)你之前计算的时间发出timeout()信号叫做start()。
如果应用程序同时激活了多个QTimer,那就没问题了;在这种情况下,SleepUntilThereIsSomethingToDo()只需迭代所有活动的QTimers以找到具有最小next-time-time戳的那个,并且仅使用该最小时间戳来计算select()的最大时间间隔应该被允许睡觉。然后在select()返回后,DoTheThingsThatNeedDoingNow()也会迭代活动计时器,并仅为那些下一个超时时间戳不大于当前时间的人发出超时信号。事件循环重复(根据需要快速或缓慢)以提供多线程行为的外观而不需要实际需要多个线程。
答案 1 :(得分:4)
查看documentation about timers和source code of QTimer
and QObject
,我们可以看到计时器正在分配给对象的线程/事件循环中运行。来自doc:
要使
main queue
起作用,您的应用程序中必须有一个事件循环;也就是说,你必须在某处调用QTimer
。定时器事件仅在事件循环运行时传递。在多线程应用程序中,您可以在具有事件循环的任何线程中使用
QCoreApplication::exec()
。要从非GUI线程启动事件循环,请使用QTimer
。 Qt使用计时器的线程亲和性来确定哪个线程将发出QThread::exec()
信号。因此,您必须在其线程中启动和停止计时器;无法从另一个线程启动计时器。
在内部,timeout()
只需使用QTimer
方法在一定时间后开火。这个本身以某种方式告诉它运行的线程在经过一段时间后才会触发。
因此,只要您不阻止事件队列,您的程序就可以持续良好地运行并跟踪计时器。如果您担心计时器不是100%准确,请尝试将长时间运行的回调移出其自己的线程中的事件队列,或者为计时器使用不同的事件队列。
答案 2 :(得分:2)
QTimer对象将其自身注册到EventDispatcher(QAbstractEventDispatcher)中,该事件将在每次特定的已注册QTimer超时时负责发送QTimerEvent类型的事件。例如,在GNU / Linux上,有一个名为QEventDispatcherUNIXPrivate的QAbstractEventDispatcher的私有实现,该实现在考虑当前平台api的情况下进行计算。 QTimerEvent从QEventDispatcherUNIXPrivate发送到QTimer对象所属的同一线程(即已创建)的事件循环的队列中。
QEventDispatcherUNIXPrivate不会由于某个OS系统事件或时钟而触发QTimerEvent,而是因为当QTimer也驻留在其中的线程事件循环调用processEvents时,它会定期检查超时。 Se在这里:https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qeventdispatcher_unix.cpp.html#_ZN27QEventDispatcherUNIXPrivateC1Ev