Timers/bg: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
 
No edit summary
Line 1: Line 1:
[[Category:QtInternals]]<br />'''Български''' [[QtTimers|English]] [[QtTimers SimplifiedChinese|简体中文]]<br />[toc align_right=&quot;yes&amp;quot; depth=&quot;2&amp;quot;]
[[Category:QtInternals]]
'''Български''' [[QtTimers|English]] [[QtTimers SimplifiedChinese|简体中文]]
[toc align_right="yes" depth="2"]


Написано от: Girish Ramakrishnan, ForwardBias Technologies
Написано от: Girish Ramakrishnan, ForwardBias Technologies
Line 5: Line 7:
= API-та за таймери =
= API-та за таймери =


Qt предоставя две API-та за работа с таймери.<br />* &quot;QObject::startTimer&amp;quot;:http://doc.qt.nokia.com/4.7/qobject.html#startTimer - Създава повтарящ се таймер за употреба от всеки подклас на QObject и връща идентификатор на таймера. Когато таймера изтече, обекта получава QEvent::Timer, който може да се обработи като се предефинира функцията QObject::timerEvent(QTimerEvent '''). Аргумента на QTimerEvent съдържа идентификатора на таймера, с цел да може да се проверява кой таймер е свършил, ако QObject използва няколко. QObject::killTimer(id) може да се използва за спиране на таймера.
Qt предоставя две API-та за работа с таймери.
<br />''' &quot;QTimer&amp;quot;:http://doc.qt.nokia.com/latest/qtimer.html - QTimer е QObject, който излъчва сигнала elapsed(), когато таймера изтече. Той просто използва QObject::startTimer() и при обработката на QObject::timerEvent(), излъчва сигнала elapsed().
* "QObject::startTimer":http://doc.qt.nokia.com/4.7/qobject.html#startTimer - Създава повтарящ се таймер за употреба от всеки подклас на QObject и връща идентификатор на таймера. Когато таймера изтече, обекта получава QEvent::Timer, който може да се обработи като се предефинира функцията QObject::timerEvent(QTimerEvent '''). Аргумента на QTimerEvent съдържа идентификатора на таймера, с цел да може да се проверява кой таймер е свършил, ако QObject използва няколко. QObject::killTimer(id) може да се използва за спиране на таймера.
 
''' "QTimer":http://doc.qt.nokia.com/latest/qtimer.html - QTimer е QObject, който излъчва сигнала elapsed(), когато таймера изтече. Той просто използва QObject::startTimer() и при обработката на QObject::timerEvent(), излъчва сигнала elapsed().


== QAbstractEventDispatcher ==
== QAbstractEventDispatcher ==


&quot;QtEventProcessing&amp;quot;:http://developer.qt.nokia.com/wiki/QtEventProcessing предоставя обобщен преглед на това как Qt обработва събитията от операционната система.
"QtEventProcessing":http://developer.qt.nokia.com/wiki/QtEventProcessing предоставя обобщен преглед на това как Qt обработва събитията от операционната система.


&quot;QAbstractEventDispatcher&amp;quot;:http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html е интерфейсът за изпомпване на събития/съобщения. За всяка нишка, създадена от Qt, съществува по един диспечер на събитията. Той изисква функциите &quot;registerTimer&amp;quot;:http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#registerTimer-2 и &quot;unregisterTimer&amp;quot;:http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#unregisterTimer да бъдат имплементирани, за да може да поддържа таймери.
"QAbstractEventDispatcher":http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html е интерфейсът за изпомпване на събития/съобщения. За всяка нишка, създадена от Qt, съществува по един диспечер на събитията. Той изисква функциите "registerTimer":http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#registerTimer-2 и "unregisterTimer":http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#unregisterTimer да бъдат имплементирани, за да може да поддържа таймери.


Има два варианта на registerTimer (един виртуален и не виртуален). И двата приемат за аргумент QObject-а, който ще използва таймера. Не виртуалният registerTimer (cross-platform) заделя уникален идентификатор и извиква виртуалния registerTimer за реалното регистриране/създаване по специфичен за платформата(ОС) начин. С този подход, платформения диспечер на събитията поддържа списък с таймерите, асоциирани с даден QObject. &quot;QAbstractEventDispatcher::registeredTimers&amp;quot;:http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#registeredTimers може да се използва за взимане на лист с таймерите, свързани с QObject.
Има два варианта на registerTimer (един виртуален и не виртуален). И двата приемат за аргумент QObject-а, който ще използва таймера. Не виртуалният registerTimer (cross-platform) заделя уникален идентификатор и извиква виртуалния registerTimer за реалното регистриране/създаване по специфичен за платформата(ОС) начин. С този подход, платформения диспечер на събитията поддържа списък с таймерите, асоциирани с даден QObject. "QAbstractEventDispatcher::registeredTimers":http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#registeredTimers може да се използва за взимане на лист с таймерите, свързани с QObject.


На ниско ниво, след като веднъж е създаден таймер, неговият интервал не може да се сменя и поради трябва да се счита за неизменяем. API-то от високо ниво трябва да създаде нов таймер, за да промени интервала. Също така, таймерите продължават да работят, докато не бъдат убити и няма концепция за единични таймери в интерфейса на диспечера на събитията. API-тата от високо ниво трябва да симулират единични таймери като спират регистрацията си за таймер след като мине първия интервал.
На ниско ниво, след като веднъж е създаден таймер, неговият интервал не може да се сменя и поради трябва да се счита за неизменяем. API-то от високо ниво трябва да създаде нов таймер, за да промени интервала. Също така, таймерите продължават да работят, докато не бъдат убити и няма концепция за единични таймери в интерфейса на диспечера на събитията. API-тата от високо ниво трябва да симулират единични таймери като спират регистрацията си за таймер след като мине първия интервал.
Line 20: Line 24:
== Алгоритъм за заделяне на идентификатори за таймери ==
== Алгоритъм за заделяне на идентификатори за таймери ==


Алгоритъма за заделяне на идентификатори за таймери е свободен и може да се разгледа в &quot;блога на Брад&amp;quot;:http://labs.trolltech.com/blogs/2008/10/22/a-never-ending-struggle/.
Алгоритъма за заделяне на идентификатори за таймери е свободен и може да се разгледа в "блога на Брад":http://labs.trolltech.com/blogs/2008/10/22/a-never-ending-struggle/.


Важно е да се отбележи, че идентификаторите на таймерите трябва да са уникални, даже и при употреба в различни нишки. Това е така, защото когато &quot;QObject&amp;quot;:http://developer.qt.nokia.com/doc/qt-4.8/qobject.html бъде преместен от нишка в друга нишка, таймерите му се преместват също (т.е сигналите и събитията вече се доставят през цикъла на събитията в новата нишка). Преместването на таймер е просто - премахва се регистрацията му от диспечера в старата нишка и се регистрира в този на новата. Ако идентификаторите на таймерите не бяха уникални, можеха да се препокрият със вече съществуващи в новата нишка. Ако нови идентификатори се генерираха при QObject::moveToThread, то приложението трябваше да бъде уведомено за промяната, а това е неудобно за разработчиците на приложения.
Важно е да се отбележи, че идентификаторите на таймерите трябва да са уникални, даже и при употреба в различни нишки. Това е така, защото когато "QObject":http://developer.qt.nokia.com/doc/qt-4.8/qobject.html бъде преместен от нишка в друга нишка, таймерите му се преместват също (т.е сигналите и събитията вече се доставят през цикъла на събитията в новата нишка). Преместването на таймер е просто - премахва се регистрацията му от диспечера в старата нишка и се регистрира в този на новата. Ако идентификаторите на таймерите не бяха уникални, можеха да се препокрият със вече съществуващи в новата нишка. Ако нови идентификатори се генерираха при QObject::moveToThread, то приложението трябваше да бъде уведомено за промяната, а това е неудобно за разработчиците на приложения.


== Unix (без glib) ==
== Unix (без glib) ==
Line 28: Line 32:
В Unix, QEventDispatcherUNIX имплементира диспечера на събитията. Той поддържа списък с таймери, сортирани по времето им на изтичане. Когато се регистрира таймер, неговото време на изтичане се пресмята като се добави периода към монотонния часовник (clock_gettime). На системи, които не поддържат такъв часовник, се използва gettimeofday.
В Unix, QEventDispatcherUNIX имплементира диспечера на събитията. Той поддържа списък с таймери, сортирани по времето им на изтичане. Когато се регистрира таймер, неговото време на изтичане се пресмята като се добави периода към монотонния часовник (clock_gettime). На системи, които не поддържат такъв часовник, се използва gettimeofday.


Диспечера разчита на select(), за да чака за събития от всички файлови дескриптори (връзки от X, сокети). При влизане в processEvents(), диспечера първо обновява периода на изтичане на всички регистрирани таймери. После предоставя времето на изтичане на системното извикване select() като интервал в първият таймер в листа. След завръщането си от ''select'', диспечера &quot;активира&amp;quot; изтеклите таймери като изпраща на съответните QObject-и QEvent::Timer.
Диспечера разчита на select(), за да чака за събития от всички файлови дескриптори (връзки от X, сокети). При влизане в processEvents(), диспечера първо обновява периода на изтичане на всички регистрирани таймери. После предоставя времето на изтичане на системното извикване select() като интервал в първият таймер в листа. След завръщането си от ''select'', диспечера "активира" изтеклите таймери като изпраща на съответните QObject-и QEvent::Timer.


Забележете, че Qt не използва POSIX API-то за таймери - timerfd_create (Защо? Преносимост?).
Забележете, че Qt не използва POSIX API-то за таймери - timerfd_create (Защо? Преносимост?).
Line 34: Line 38:
== Unix (с glib) ==
== Unix (с glib) ==


Qt може да се компилира така, че да използва цикъла на събитията на glib (по подразбиране), което помага за по-добрата интеграция с Gtk. &quot;Документация на цикъла в Glib&amp;quot;:http://developer.symbian.org/main/documentation/reference/s3/pdk/GUID-7FD05006-09C1-4EF4-A2EB-AD98C2FA8866.html&amp;quot; предоставя допълнителна информация за това как той е реализиран.
Qt може да се компилира така, че да използва цикъла на събитията на glib (по подразбиране), което помага за по-добрата интеграция с Gtk. "Документация на цикъла в Glib":http://developer.symbian.org/main/documentation/reference/s3/pdk/GUID-7FD05006-09C1-4EF4-A2EB-AD98C2FA8866.html" предоставя допълнителна информация за това как той е реализиран.


Точно като QEventDispatcherUNIX, QEventDispatcherGlib управлява таймерите като сортиран по време на изтичане лист. Той създава нов GSource за таймери. Функциите за подготовка, проверката и изпращане на този GSource работят по очевидния начин.
Точно като QEventDispatcherUNIX, QEventDispatcherGlib управлява таймерите като сортиран по време на изтичане лист. Той създава нов GSource за таймери. Функциите за подготовка, проверката и изпращане на този GSource работят по очевидния начин.
Line 40: Line 44:
== Windows ==
== Windows ==


Както е отбелязано в &quot;QtEventProcessing&amp;quot;:http://developer.qt.nokia.com/wiki/QtEventProcessing, Qt създава скрит прозорец с указател към функция(callback), за да обработва събития. Когато се регистрира таймер, QEventDispatcherWin създава вграден Windows таймер, базирайки се на интервала.
Както е отбелязано в "QtEventProcessing":http://developer.qt.nokia.com/wiki/QtEventProcessing, Qt създава скрит прозорец с указател към функция(callback), за да обработва събития. Когато се регистрира таймер, QEventDispatcherWin създава вграден Windows таймер, базирайки се на интервала.


* Ако интервалът е по-голям от 20 милисекунди, се използва SetTimer (с идентификатор, създаден чрез QAbstractEventDispatcher::registerTimer). SetTimer изпраща съобщение WM_TIMER към подадената функция, което се препраща като QEvent::Timer към QObject.
* Ако интервалът е по-голям от 20 милисекунди, се използва SetTimer (с идентификатор, създаден чрез QAbstractEventDispatcher::registerTimer). SetTimer изпраща съобщение WM_TIMER към подадената функция, което се препраща като QEvent::Timer към QObject.


* Ако интервалът е по-малък от 20 милисекунди, Qt се опитва да използва мултимедийните (известни също като &quot;бързи&amp;quot;) таймери чрез timeSetEvent. timeSetEvent приема указател към функция, която се извиква при изтичане и това става в отделна нишка. В Qt обаче, таймерите работят в същата нишка, в която е и QObject. За това функцията, подадена на timeSetEventlback, пуска съобщение към диспечера, което той прихваща и предава като QEvent::Timer.
* Ако интервалът е по-малък от 20 милисекунди, Qt се опитва да използва мултимедийните (известни също като "бързи") таймери чрез timeSetEvent. timeSetEvent приема указател към функция, която се извиква при изтичане и това става в отделна нишка. В Qt обаче, таймерите работят в същата нишка, в която е и QObject. За това функцията, подадена на timeSetEventlback, пуска съобщение към диспечера, което той прихваща и предава като QEvent::Timer.


* Ако интервалът е 0, диспечера създава специално съобщение към себе си (QEvent::ZeroTimerEvent) при регистрация на таймера. После той обработва това събитие като изпраща QEvent::Timer към съответния QObject и ZeroTimerEvent към себе си.
* Ако интервалът е 0, диспечера създава специално съобщение към себе си (QEvent::ZeroTimerEvent) при регистрация на таймера. После той обработва това събитие като изпраща QEvent::Timer към съответния QObject и ZeroTimerEvent към себе си.
Line 50: Line 54:
== Реализация на API-то ==
== Реализация на API-то ==


QObject::startTimer регистрира нов таймер в диспечера на събитията. Това е приблизително еквивалентно на, QAbstractEventDispatcher::instance(object-&gt;thread())-&gt;registerTimer(интервал, обект).
QObject::startTimer регистрира нов таймер в диспечера на събитията. Това е приблизително еквивалентно на, QAbstractEventDispatcher::instance(object->thread())->registerTimer(интервал, обект).


QTimer е QObject. Всичко, което прави е да извика QObject::startTimer(). Той излъчва сигнала activated() в своя timerEvent().
QTimer е QObject. Всичко, което прави е да извика QObject::startTimer(). Той излъчва сигнала activated() в своя timerEvent().
Line 56: Line 60:
== QBasicTimer ==
== QBasicTimer ==


QTimer изглежда тежък, тъй като е QObject и често се избягва в Qt код. В същото време, използването на QObject::startTimer() API-то предразполага към грешки.<br /># Когато спирате таймер, трябва да направите идентификатора му невалиден (примерно да го приравните на –1) след QObject::killTimer(m_timerId). Това се изисква, тъй като положителна стойност на m_timerId ще индикира, че таймера е активен.
QTimer изглежда тежък, тъй като е QObject и често се избягва в Qt код. В същото време, използването на QObject::startTimer() API-то предразполага към грешки.
# Когато спирате таймер, трябва да направите идентификатора му невалиден (примерно да го приравните на –1) след QObject::killTimer(m_timerId). Това се изисква, тъй като положителна стойност на m_timerId ще индикира, че таймера е активен.
 
# За да рестартирате таймер, трябва да запомните, че първо трябва да спрете предишния таймер, ако m_timerId е различно от -1.


# За да рестартирате таймер, трябва да запомните, че първо трябва да спрете предишния таймер, ако m_timerId е различно от <s>1.
QBasicTimer решава горният проблем и е просто подобрен интерфейс около "идентификатора на таймера". QBasicTimer::start(int msec, QObject *) спира всички съществуващи таймери към този обект и създава нов, използвайки object->startTimer(msec). QBasicTimer::stop() спира таймера и слага идентификатора му на невалидна стойност. QBasicTimer::timerId() предоставя идентификатора.
<br />QBasicTimer решава горният проблем и е просто подобрен интерфейс около &quot;идентификатора на таймера&amp;quot;. QBasicTimer::start(int msec, QObject *) спира всички съществуващи таймери към този обект и създава нов, използвайки object</s>&gt;startTimer(msec). QBasicTimer::stop() спира таймера и слага идентификатора му на невалидна стойност. QBasicTimer::timerId() предоставя идентификатора.


== Още материали ==
== Още материали ==

Revision as of 12:46, 25 February 2015

Български English 简体中文 [toc align_right="yes" depth="2"]

Написано от: Girish Ramakrishnan, ForwardBias Technologies

API-та за таймери

Qt предоставя две API-та за работа с таймери.

  • "QObject::startTimer":http://doc.qt.nokia.com/4.7/qobject.html#startTimer - Създава повтарящ се таймер за употреба от всеки подклас на QObject и връща идентификатор на таймера. Когато таймера изтече, обекта получава QEvent::Timer, който може да се обработи като се предефинира функцията QObject::timerEvent(QTimerEvent ). Аргумента на QTimerEvent съдържа идентификатора на таймера, с цел да може да се проверява кой таймер е свършил, ако QObject използва няколко. QObject::killTimer(id) може да се използва за спиране на таймера.

"QTimer":http://doc.qt.nokia.com/latest/qtimer.html - QTimer е QObject, който излъчва сигнала elapsed(), когато таймера изтече. Той просто използва QObject::startTimer() и при обработката на QObject::timerEvent(), излъчва сигнала elapsed().

QAbstractEventDispatcher

"QtEventProcessing":http://developer.qt.nokia.com/wiki/QtEventProcessing предоставя обобщен преглед на това как Qt обработва събитията от операционната система.

"QAbstractEventDispatcher":http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html е интерфейсът за изпомпване на събития/съобщения. За всяка нишка, създадена от Qt, съществува по един диспечер на събитията. Той изисква функциите "registerTimer":http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#registerTimer-2 и "unregisterTimer":http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#unregisterTimer да бъдат имплементирани, за да може да поддържа таймери.

Има два варианта на registerTimer (един виртуален и не виртуален). И двата приемат за аргумент QObject-а, който ще използва таймера. Не виртуалният registerTimer (cross-platform) заделя уникален идентификатор и извиква виртуалния registerTimer за реалното регистриране/създаване по специфичен за платформата(ОС) начин. С този подход, платформения диспечер на събитията поддържа списък с таймерите, асоциирани с даден QObject. "QAbstractEventDispatcher::registeredTimers":http://doc.qt.nokia.com/latest/qabstracteventdispatcher.html#registeredTimers може да се използва за взимане на лист с таймерите, свързани с QObject.

На ниско ниво, след като веднъж е създаден таймер, неговият интервал не може да се сменя и поради трябва да се счита за неизменяем. API-то от високо ниво трябва да създаде нов таймер, за да промени интервала. Също така, таймерите продължават да работят, докато не бъдат убити и няма концепция за единични таймери в интерфейса на диспечера на събитията. API-тата от високо ниво трябва да симулират единични таймери като спират регистрацията си за таймер след като мине първия интервал.

Алгоритъм за заделяне на идентификатори за таймери

Алгоритъма за заделяне на идентификатори за таймери е свободен и може да се разгледа в "блога на Брад":http://labs.trolltech.com/blogs/2008/10/22/a-never-ending-struggle/.

Важно е да се отбележи, че идентификаторите на таймерите трябва да са уникални, даже и при употреба в различни нишки. Това е така, защото когато "QObject":http://developer.qt.nokia.com/doc/qt-4.8/qobject.html бъде преместен от нишка в друга нишка, таймерите му се преместват също (т.е сигналите и събитията вече се доставят през цикъла на събитията в новата нишка). Преместването на таймер е просто - премахва се регистрацията му от диспечера в старата нишка и се регистрира в този на новата. Ако идентификаторите на таймерите не бяха уникални, можеха да се препокрият със вече съществуващи в новата нишка. Ако нови идентификатори се генерираха при QObject::moveToThread, то приложението трябваше да бъде уведомено за промяната, а това е неудобно за разработчиците на приложения.

Unix (без glib)

В Unix, QEventDispatcherUNIX имплементира диспечера на събитията. Той поддържа списък с таймери, сортирани по времето им на изтичане. Когато се регистрира таймер, неговото време на изтичане се пресмята като се добави периода към монотонния часовник (clock_gettime). На системи, които не поддържат такъв часовник, се използва gettimeofday.

Диспечера разчита на select(), за да чака за събития от всички файлови дескриптори (връзки от X, сокети). При влизане в processEvents(), диспечера първо обновява периода на изтичане на всички регистрирани таймери. После предоставя времето на изтичане на системното извикване select() като интервал в първият таймер в листа. След завръщането си от select, диспечера "активира" изтеклите таймери като изпраща на съответните QObject-и QEvent::Timer.

Забележете, че Qt не използва POSIX API-то за таймери - timerfd_create (Защо? Преносимост?).

Unix (с glib)

Qt може да се компилира така, че да използва цикъла на събитията на glib (по подразбиране), което помага за по-добрата интеграция с Gtk. "Документация на цикъла в Glib":http://developer.symbian.org/main/documentation/reference/s3/pdk/GUID-7FD05006-09C1-4EF4-A2EB-AD98C2FA8866.html" предоставя допълнителна информация за това как той е реализиран.

Точно като QEventDispatcherUNIX, QEventDispatcherGlib управлява таймерите като сортиран по време на изтичане лист. Той създава нов GSource за таймери. Функциите за подготовка, проверката и изпращане на този GSource работят по очевидния начин.

Windows

Както е отбелязано в "QtEventProcessing":http://developer.qt.nokia.com/wiki/QtEventProcessing, Qt създава скрит прозорец с указател към функция(callback), за да обработва събития. Когато се регистрира таймер, QEventDispatcherWin създава вграден Windows таймер, базирайки се на интервала.

  • Ако интервалът е по-голям от 20 милисекунди, се използва SetTimer (с идентификатор, създаден чрез QAbstractEventDispatcher::registerTimer). SetTimer изпраща съобщение WM_TIMER към подадената функция, което се препраща като QEvent::Timer към QObject.
  • Ако интервалът е по-малък от 20 милисекунди, Qt се опитва да използва мултимедийните (известни също като "бързи") таймери чрез timeSetEvent. timeSetEvent приема указател към функция, която се извиква при изтичане и това става в отделна нишка. В Qt обаче, таймерите работят в същата нишка, в която е и QObject. За това функцията, подадена на timeSetEventlback, пуска съобщение към диспечера, което той прихваща и предава като QEvent::Timer.
  • Ако интервалът е 0, диспечера създава специално съобщение към себе си (QEvent::ZeroTimerEvent) при регистрация на таймера. После той обработва това събитие като изпраща QEvent::Timer към съответния QObject и ZeroTimerEvent към себе си.

Реализация на API-то

QObject::startTimer регистрира нов таймер в диспечера на събитията. Това е приблизително еквивалентно на, QAbstractEventDispatcher::instance(object->thread())->registerTimer(интервал, обект).

QTimer е QObject. Всичко, което прави е да извика QObject::startTimer(). Той излъчва сигнала activated() в своя timerEvent().

QBasicTimer

QTimer изглежда тежък, тъй като е QObject и често се избягва в Qt код. В същото време, използването на QObject::startTimer() API-то предразполага към грешки.

  1. Когато спирате таймер, трябва да направите идентификатора му невалиден (примерно да го приравните на –1) след QObject::killTimer(m_timerId). Това се изисква, тъй като положителна стойност на m_timerId ще индикира, че таймера е активен.
  1. За да рестартирате таймер, трябва да запомните, че първо трябва да спрете предишния таймер, ако m_timerId е различно от -1.

QBasicTimer решава горният проблем и е просто подобрен интерфейс около "идентификатора на таймера". QBasicTimer::start(int msec, QObject *) спира всички съществуващи таймери към този обект и създава нов, използвайки object->startTimer(msec). QBasicTimer::stop() спира таймера и слага идентификатора му на невалидна стойност. QBasicTimer::timerId() предоставя идентификатора.

Още материали