Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.

Timers/bg

From Qt Wiki
Jump to navigation Jump to search
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine.
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.

Български English 简体中文

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

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

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

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

QTimer - QTimer е QObject, който излъчва сигнала elapsed(), когато таймера изтече. Той просто използва QObject::startTimer() и при обработката на QObject::timerEvent(), излъчва сигнала elapsed().

QAbstractEventDispatcher

QtEventProcessing предоставя обобщен преглед на това как Qt обработва събитията от операционната система.

QAbstractEventDispatcher е интерфейсът за изпомпване на събития/съобщения. За всяка нишка, създадена от Qt, съществува по един диспечер на събитията. Той изисква функциите registerTimer и unregisterTimer да бъдат имплементирани, за да може да поддържа таймери.

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

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

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

Алгоритъма за заделяне на идентификатори за таймери е свободен и може да се разгледа в блога на Брад.

Важно е да се отбележи, че идентификаторите на таймерите трябва да са уникални, даже и при употреба в различни нишки. Това е така, защото когато QObject бъде преместен от нишка в друга нишка, таймерите му се преместват също (т.е сигналите и събитията вече се доставят през цикъла на събитията в новата нишка). Преместването на таймер е просто - премахва се регистрацията му от диспечера в старата нишка и се регистрира в този на новата. Ако идентификаторите на таймерите не бяха уникални, можеха да се препокрият със вече съществуващи в новата нишка. Ако нови идентификатори се генерираха при 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" предоставя допълнителна информация за това как той е реализиран.

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

Windows

Както е отбелязано в 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() предоставя идентификатора.

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