Saltar al contenido

¿Se ejecuta un objeto QTimer en un hilo separado? ¿Cuál es su mecanismo?

Si encuentras algún fallo con tu código o trabajo, recuerda probar siempre en un entorno de testing antes aplicar el código al proyecto final.

Solución:

Cuando creo un objeto QTimer en Qt 5 y lo inicio usando la función miembro start (), ¿se crea un hilo separado que realiza un seguimiento del tiempo y llama a la función timeout () a intervalos regulares?

No; crear un hilo por separado sería costoso y no es necesario, por lo que no es así como se implementa QTimer.

Aquí, ¿cómo sabe el programa cuándo se agota el tiempo de espera ()?

El método QTimer :: start () puede llamar a una función de hora del sistema (por ejemplo, gettimeofday () o similar) para averiguar (en unos pocos milisegundos) a qué hora se llamó a start (). Luego puede agregar diez milisegundos (o cualquier valor que haya especificado) a ese tiempo y ahora tiene un registro que indica cuándo se supone que se emitirá la señal de tiempo de espera () a continuación.

Entonces, teniendo esa información, ¿qué hace entonces para asegurarse de que eso suceda?

los key El hecho de saber es que QTimer timeout-signal -mission solo funciona si / cuando su programa Qt se está ejecutando dentro del bucle de eventos de Qt. Casi todos los programas de Qt tendrán algo como esto, generalmente cerca de la parte inferior de su función main ():

QApplication app(argc, argv);
[...]
app.exec();

Tenga en cuenta que en una aplicación típica, casi todo el tiempo de la aplicación se gastará dentro de esa llamada exec (); es decir, la llamada app.exec () no volverá hasta que llegue el momento de salir de la aplicación.

Entonces, ¿qué está pasando dentro de esa llamada exec () mientras su programa se está ejecutando? Con una biblioteca grande y compleja como Qt es necesariamente complicado, pero no es una simplificación demasiado decir que está ejecutando un bucle de eventos que se ve conceptualmente algo como esto:

 while(1)
 
     SleepUntilThereIsSomethingToDo();  // not a real function name!
     DoTheThingsThatNeedDoingNow();     // this is also a name I made up
     if (timeToQuit) break;
 

Entonces, cuando su aplicación está inactiva, el proceso se suspenderá dentro de la llamada SleepUntilThereIsSomethingToDo (), pero tan pronto como llegue un evento que necesite ser manejado (por ejemplo, el usuario mueve el mouse o presiona un key, o los datos llegan a un socket, etc.), SleepUntilThereIsSomethingToDo () regresará y luego se ejecutará el código para responder a ese evento, lo que dará como resultado la acción adecuada, como la actualización de los widgets o la llamada de la señal timeout ().

Entonces, ¿cómo sabe SleepUntilThereIsSomethingToDo () cuándo es el momento de despertarse y regresar? Esto variará mucho según el sistema operativo en el que esté ejecutando, ya que diferentes sistemas operativos tienen diferentes API para manejar este tipo de cosas, pero una forma clásica de UNIX-y de implementar tal función sería con la llamada a POSIX select ():

int select(int nfds, 
           fd_set *readfds, 
           fd_set *writefds,
           fd_set *exceptfds, 
           struct timeval *timeout);

Tenga en cuenta que select () toma tres argumentos fd_set diferentes, cada uno de los cuales puede especificar un número de descriptores de archivo; al pasar los objetos fd_set apropiados a esos argumentos, puede hacer que select () se active en el instante en que una operación de E / S sea posible en cualquiera de un conjunto de descriptores de archivo que desea monitorear, de modo que su programa pueda manejar el E / S sin demora. Sin embargo, la parte interesante para nosotros es el argumento final, que es un argumento de tiempo de espera. En particular, puede pasar un struct timeval objeto aquí que dice seleccionar (): “Si no se han producido eventos de E / S después de (tantos) microsegundos, entonces debería darse por vencido y regresar de todos modos”.

Eso resulta ser muy útil, porque al usar ese parámetro, la función SleepUntilThereIsSomethingToDo () puede hacer algo como esto (pseudocódigo):

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 ...]
   

Con suerte, eso tiene sentido, y puede ver cómo el bucle de eventos usa el argumento de tiempo de espera de select () para permitir que se despierte y emita la señal de tiempo de espera () en (aproximadamente) el tiempo que había calculado previamente cuando llamó a start ( ).

Por cierto, si la aplicación tiene más de un QTimer activo simultáneamente, no hay problema; en ese caso, SleepUntilThereIsSomethingToDo () solo necesita iterar sobre todos los QTimers activos para encontrar el que tiene la marca de tiempo del próximo tiempo de espera más pequeña, y usar solo esa marca de tiempo mínima para calcular el intervalo de tiempo máximo que seleccione () se le debe permitir dormir. Luego, después de que select () devuelve, DoTheThingsThatNeedDoingNow () también itera sobre los temporizadores activos y emite una señal de tiempo de espera solo para aquellos cuya próxima marca de tiempo de espera no es mayor que la hora actual. El bucle de eventos se repite (tan rápido o tan lentamente como sea necesario) para dar una apariencia de comportamiento multiproceso sin que realmente se requieran múltiples subprocesos.

Mirando la documentación sobre temporizadores y el código fuente de QTimer y QObject podemos ver que el temporizador se está ejecutando en el ciclo de subproceso / evento que está asignado al objeto. Del doc:

Para QTimer para que funcione, debe tener un bucle de eventos en su aplicación; es decir, debes llamar QCoreApplication::exec() algun lado. Los eventos del temporizador se entregarán solo mientras se esté ejecutando el bucle de eventos.

En aplicaciones multiproceso, puede utilizar QTimer en cualquier hilo que tenga un bucle de eventos. Para iniciar un bucle de eventos desde un hilo sin GUI, use QThread::exec(). Qt usa la afinidad de subprocesos del temporizador para determinar qué subproceso emitirá el timeout() señal. Debido a esto, debe iniciar y detener el temporizador en su hilo; no es posible iniciar un temporizador desde otro hilo.

Internamente, QTimer simplemente usa el QObject::startTimer método para disparar después de una cierta cantidad de tiempo. Este en sí mismo de alguna manera le dice al hilo que se está ejecutando para que se active después de la cantidad de tiempo.

Por lo tanto, su programa se ejecuta bien de forma continua y realiza un seguimiento de los temporizadores siempre que no bloquee la cola de eventos. Si le preocupa que su temporizador no sea 100% preciso, intente sacar las devoluciones de llamada de larga duración de la cola de eventos en su propio hilo, o use una cola de eventos diferente para los temporizadores.

El objeto QTimer se registra en EventDispatcher (QAbstractEventDispatcher) que se encarga de enviar eventos de tipo QTimerEvent cada vez que hay un tiempo de espera para un QTimer registrado en particular. Por ejemplo, en GNU / Linux hay una implementación privada de QAbstractEventDispatcher llamada QEventDispatcherUNIXPrivate que hace cálculos teniendo en cuenta la api de la plataforma en ese momento. Los QTimerEvent se envían desde QEventDispatcherUNIXPrivate a la cola del bucle de eventos del mismo hilo al que pertenece el objeto QTimer, es decir, se creó.

QEventDispatcherUNIXPrivate no dispara un QTimerEvent debido a algún evento o reloj del sistema operativo, sino porque comprueba periódicamente el tiempo de espera cuando el bucle de eventos del hilo llama a processEvents donde también vive QTimer. Vea aquí: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qeventdispatcher_unix.cpp.html#_ZN27QEventDispatcherUNIXPrivateC1Ev

Reseñas y calificaciones

Acuérdate de que te concedemos explicar si te fue preciso.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *