Когда я создаю объект QTimer в Qt 5, и запустить его с помощью запуска() функцию-член, отдельный поток создан, который следит за времени и вызывает функцию тайм-аут() через регулярные промежутки времени ?
Нет; создание отдельного потока было бы дорогостоящим, и это не обязательно, так что это не так, как QTimer реализован.
Здесь, как программа знает, когда происходит тайм-аут()? Метод
QTimer :: заводится() может вызвать системное время функции (например, gettimeofday() или аналогичный), чтобы выяснить (в течение нескольких миллисекунд), что время было то, что начало() была вызвана. Затем он может добавить к этому времени десять миллисекунд (или любое другое значение), и теперь у него есть запись, указывающая, когда будет выходить сигнал таймаута().
Итак, имея эту информацию, что она делает, чтобы убедиться, что это происходит?
Ключевым фактом является то, что тайм-аут QTimer-сигнал-излучение работает только в том случае, если ваша программа Qt выполняется внутри цикла событий Qt. Просто о каждой программе Qt будет иметь что-то вроде этого, как правило, в нижней части своей основной функции():
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() будет а затем код для ответа на это событие будет выполнен, что приведет к соответствующим действиям, таким как обновление виджетов или вызванный сигнал таймаута().
Так как же 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 без задержки. Однако интересная часть для нас - это последний аргумент, который является аргументом тайм-аута. В частности, вы можете передать объект struct timeval
здесь, который говорит select(): «Если после (этого много) микросекунд не произошло событий ввода-вывода, вам следует просто отказаться и вернуться в любом случае».
Это оказывается очень полезным, так как с помощью этого параметра, функция 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(), чтобы позволить ему проснуться и испустить сигнал таймаута() в (приблизительно) времени, которое оно ранее вычисляло при вызове start().
Btw, если приложение имеет одновременно несколько активных QTimer, это не проблема; в этом случае SleepUntilThereIsSomethingToDo() просто нужно перебрать все активные QTimers, чтобы найти ту, которая имеет наименьшую отметку времени следующего таймаута, и использовать только ту минимальную метку времени для вычисления максимального интервала времени, который выбирает() должно быть разрешено спать. Затем после возвращения select() DoTheThingsThatNeedDoingNow() также выполняет итерацию по активным таймерам и испускает сигнал тайм-аута только для тех, чья печать следующего тайма-времени не больше текущего времени. Цикл событий повторяется (как можно быстрее или медленнее, если необходимо), чтобы создать видимость многопоточного поведения, фактически не требуя нескольких потоков.
Большое спасибо! Это было очень полезно. У меня есть один запрос - в функции 'SleepUntilThereIsSomethingToDo()' функция возвращается раньше, если нужно обработать некоторый вход. Если 'if (now> = nextQTimerTime)' еще не верно, то тайм-аут не будет испускаться в 'DoTheThingsThatNeedDoingNow()'. Позже, когда снова в 'SleepUntilThereIsSomethingToDo()', таймер сбрасывается. Итак, не один «тайм-аут()» пропущен здесь? Или я неправильно понял? Спасибо. – GoodDeeds
Интервал таймаута пересчитывается для каждого вызова SleepUntil(), но таймер не сбрасывается и тайм-аут не пропускается. Например: скажем, при 2PM вы вызываете QTimer :: start() и устанавливаете интервал QTimer на один час.В следующем вызове SleepUntil() рассчитывается таймаут (3 PM-2PM = 1 час). Но в 2:30 вечера поступает какой-то вход, поэтому select() возвращается, и вход обрабатывается. Теперь SleepUntil() снова вызывается и вычисляет новый таймаут (3 PM-2:30PM = 30 минут). Таким образом (при отсутствии дополнительных входных данных), он все равно просыпается и излучает сигнал при 3PM. –
Значит ли это, что 'nextQTimerTime' хранится где-то в другом месте, доступном для обеих функций? – GoodDeeds