2017-02-16 2 views
9

Когда я создаю объект QTimer в Qt 5 и запускаю его с помощью функции-члена start(), создается отдельный поток, который отслеживает время и вызывает функцию timeout() через равные промежутки времени?Выполняется ли объект QTimer в отдельном потоке? Каков его механизм?

Например,

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() обрабатывается ОС? Есть ли какое-то оборудование, которое отслеживает время и отправляет прерывания через соответствующие промежутки времени? Но если это так, как много таймеров могут запускаться одновременно и независимо, как можно отдельно отслеживать каждый таймер?

Каков механизм?

спасибо.

ответ

11

Когда я создаю объект 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() также выполняет итерацию по активным таймерам и испускает сигнал тайм-аута только для тех, чья печать следующего тайма-времени не больше текущего времени. Цикл событий повторяется (как можно быстрее или медленнее, если необходимо), чтобы создать видимость многопоточного поведения, фактически не требуя нескольких потоков.

+0

Большое спасибо! Это было очень полезно. У меня есть один запрос - в функции 'SleepUntilThereIsSomethingToDo()' функция возвращается раньше, если нужно обработать некоторый вход. Если 'if (now> = nextQTimerTime)' еще не верно, то тайм-аут не будет испускаться в 'DoTheThingsThatNeedDoingNow()'. Позже, когда снова в 'SleepUntilThereIsSomethingToDo()', таймер сбрасывается. Итак, не один «тайм-аут()» пропущен здесь? Или я неправильно понял? Спасибо. – GoodDeeds

+1

Интервал таймаута пересчитывается для каждого вызова SleepUntil(), но таймер не сбрасывается и тайм-аут не пропускается. Например: скажем, при 2PM вы вызываете QTimer :: start() и устанавливаете интервал QTimer на один час.В следующем вызове SleepUntil() рассчитывается таймаут (3 PM-2PM = 1 час). Но в 2:30 вечера поступает какой-то вход, поэтому select() возвращается, и вход обрабатывается. Теперь SleepUntil() снова вызывается и вычисляет новый таймаут (3 PM-2:30PM = 30 минут). Таким образом (при отсутствии дополнительных входных данных), он все равно просыпается и излучает сигнал при 3PM. –

+0

Значит ли это, что 'nextQTimerTime' хранится где-то в другом месте, доступном для обеих функций? – GoodDeeds

3

Глядя на documentation about timers и на source code of QTimer and QObject, мы видим, что таймер работает в цикле потока/события, назначенного объекту. Из документа:

Для работы QTimer в вашей заявке должен быть установлен цикл событий; то есть вы должны позвонить QCoreApplication::exec(). События таймера будут доставлены только при запуске цикла событий.

В многопоточных приложениях вы можете использовать QTimer в любом потоке, который имеет цикл событий. Чтобы запустить цикл событий из потока, отличного от GUI, используйте QThread::exec(). Qt использует аффинность потока таймера, чтобы определить, какой поток будет излучать сигнал timeout(). Из-за этого вы должны запустить и остановить таймер в своем потоке; невозможно запустить таймер из другого потока.

Внутренний, QTimer просто использует способ QObject::startTimer для стрельбы через определенное время. Этот сам как-то сообщает, что поток, который он запускает, запускается после количества времени.

Так что ваша программа прекрасно работает непрерывно и отслеживает таймеры, пока вы не блокируете очередь событий. Если вы беспокоитесь о том, что ваш таймер не на 100% точнее, попробуйте переместить длинные обратные вызовы из очереди событий в своем потоке или использовать другую очередь событий для таймеров.

+0

Спасибо. Но как может «поток контроля» программы выполняет две вещи в одном потоке - последовательное выполнение операторов, а также отслеживание времени? Во-вторых, какова роль цикла событий в этом? – GoodDeeds

+0

@GoodDeeds Чтобы ответить на вопрос о вашем управлении потоком, вы должны сообщить нам, что делает ваше приложение. Но я не могу рассказать вам, как сам таймер реализован - я мог бы подумать, что a) они сравнивают текущее время со временем, которое должно срабатывать, или b) использовать некоторые функции прерывания аппаратной/операционной системы. – msrd0

+0

Спасибо. Я нашел этот ответ http://stackoverflow.com/a/1472579/5987698 (добавленный также к моему вопросу), который, кажется, дает представление о второй части комментария - кажется, что (b) верно. Но так ли это? Кроме того, если он работал, как указано в (a), не будет ли какой-нибудь поток периодически проверять время? Разве это не приведет к неточностям? – GoodDeeds

Смежные вопросы