Lorsque je crée un objet
QTimer
dans Qt 5 et que je le démarre à l'aide de la fonction membrestart()
, un thread distinct créé crée un suivi de l'heure et appelle la fonctiontimeout()
fonctionne à intervalles réguliers?
Par exemple,
QTimer *timer = new QTimer;
timer->start(10);
connect(timer,SIGNAL(timeout()),someObject,SLOT(someFunction()));
Ici, comment le programme sait-il quand timeout()
se produit? Je pense qu'il devrait fonctionner dans un thread séparé, car je ne vois pas comment un programme séquentiel pourrait suivre l'heure et continuer son exécution simultanément. Cependant, je n'ai pu trouver aucune information à ce sujet dans la documentation Qt ou ailleurs pour le confirmer.
J'ai lu la documentation officielle , et certaines questions sur StackOverflow telles que this et this semblent très liées, mais je n'ai pas pu obtenir ma réponse via leur.
Quelqu'un pourrait-il expliquer le mécanisme par lequel un objet QTimer
fonctionne?
En cherchant plus loin, j'ai trouvé que selon cette réponse par Bill , il est mentionné que
Les événements sont livrés de manière asynchrone par le système d'exploitation, c'est pourquoi il semble qu'il se passe autre chose. Il y en a, mais pas dans votre programme.
Cela signifie-t-il que timeout()
est géré par l'OS? Existe-t-il du matériel qui garde l'heure et envoie des interruptions à intervalles appropriés? Mais si c'est le cas, comme de nombreux temporisateurs peuvent fonctionner simultanément et indépendamment, comment chaque temporisateur peut-il être suivi séparément?
Quel est le mécanisme?
Merci.
Lorsque je crée un objet QTimer dans Qt 5 et que je le démarre à l'aide de la fonction membre start (), un thread distinct est-il créé qui garde la trace de l'heure et appelle la fonction timeout () à intervalles réguliers?
Non; créer un thread séparé coûterait cher et ce n'est pas nécessaire, ce n'est donc pas ainsi que QTimer est implémenté.
Ici, comment le programme sait-il quand timeout () se produit?
La méthode QTimer :: start () peut appeler une fonction d'heure système (par exemple gettimeofday () ou similaire) pour savoir (à quelques millisecondes près) l'heure à laquelle l'appel start () a été appelé. Il peut ensuite ajouter dix millisecondes (ou la valeur que vous avez spécifiée) à ce moment et il a maintenant un enregistrement indiquant quand le signal timeout () doit être émis ensuite.
Donc, avoir ces informations, que fait-il pour s'assurer que cela se produit?
Le fait clé à savoir est que l'émission de signal de temporisation QTimer ne fonctionne que si/lorsque votre programme Qt s'exécute dans la boucle d'événements de Qt. Presque chaque programme Qt aura quelque chose comme ça, généralement près du bas de sa fonction main ():
QApplication app(argc, argv);
[...]
app.exec();
Notez que dans une application typique, presque tout le temps de l'application sera passé à l'intérieur de cet appel exec (); c'est-à-dire que l'appel app.exec () ne retournera pas tant qu'il n'est pas temps pour l'application de quitter.
Alors, que se passe-t-il à l'intérieur de cet appel exec () pendant l'exécution de votre programme? Avec une grande bibliothèque complexe comme Qt, c'est nécessairement compliqué, mais ce n'est pas trop de simplification de dire qu'il exécute une boucle d'événements qui ressemble conceptuellement à quelque chose comme ceci:
while(1)
{
SleepUntilThereIsSomethingToDo(); // not a real function name!
DoTheThingsThatNeedDoingNow(); // this is also a name I made up
if (timeToQuit) break;
}
Ainsi, lorsque votre application est inactive, le processus est mis en veille à l'intérieur de l'appel SleepUntilThereIsSomethingToDo (), mais dès qu'un événement arrive qui doit être géré (par exemple, l'utilisateur déplace la souris, ou appuie sur une touche, ou les données arrivent sur un socket , ou etc), SleepUntilThereIsSomethingToDo () renverra, puis le code pour répondre à cet événement sera exécuté, entraînant l'action appropriée telle que la mise à jour des widgets ou le signal timeout () appelé.
Alors, comment SleepUntilThereIsSomethingToDo () sait-il quand il est temps de se réveiller et de revenir? Cela variera considérablement en fonction du système d'exploitation sur lequel vous exécutez, car différents systèmes d'exploitation ont différentes API pour gérer ce genre de chose, mais un moyen UNIX-y classique pour implémenter une telle fonction serait avec le POSIX select () appel:
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
Notez que select () prend trois arguments fd_set différents, chacun pouvant spécifier un certain nombre de descripteurs de fichier; en passant les objets fd_set appropriés à ces arguments, vous pouvez faire en sorte que select () se réveille au moment où une opération d'E/S devient possible sur l'un des descripteurs de fichiers que vous souhaitez surveiller, afin que votre programme puisse ensuite gérer le E/S sans délai. Cependant, la partie intéressante pour nous est l'argument final, qui est un argument de temporisation. En particulier, vous pouvez passer un struct timeval
objet ici qui dit de sélectionner (): "Si aucun événement d'E/S ne s'est produit après (autant de microsecondes), alors vous devez simplement abandonner et revenir quand même".
Cela s'avère très utile, car en utilisant ce paramètre, la fonction SleepUntilThereIsSomethingToDo () peut faire quelque chose comme ça (pseudocode):
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 ...]
}
J'espère que cela a du sens, et vous pouvez voir comment la boucle d'événements utilise l'argument timeout de select () pour lui permettre de se réveiller et d'émettre le signal timeout () à (approximativement) l'heure qu'il avait précédemment calculée lorsque vous avez appelé start ( ).
Btw si l'application a plus d'un QTimer actif simultanément, ce n'est pas un problème; dans ce cas, SleepUntilThereIsSomethingToDo () a juste besoin d'itérer sur tous les QTimers actifs pour trouver celui avec le plus petit horodatage du prochain délai d'expiration, et utiliser uniquement cet horodatage minimum pour son calcul de l'intervalle de temps maximal qui sélectionne () devrait être autorisé à dormir. Ensuite, après le retour de select (), DoTheThingsThatNeedDoingNow () itère également sur les temporisateurs actifs et émet un signal de temporisation uniquement pour ceux dont l'horodatage de temporisation suivant n'est pas supérieur à l'heure actuelle. La boucle d'événements se répète (aussi rapidement ou aussi lentement que nécessaire) pour donner un semblant de comportement multithread sans nécessiter réellement plusieurs threads.
En regardant le documentation sur les timers et le code source de QTimer
et QObject
nous pouvons voir que le timer est en cours d'exécution dans le thread/boucle d'événements affectée à l'objet. Du doc:
Pour que
QTimer
fonctionne, vous devez avoir une boucle d'événement dans votre application; c'est-à-dire que vous devez appelerQCoreApplication::exec()
quelque part. Les événements de minuterie ne seront délivrés que lorsque la boucle d'événements est en cours d'exécution.Dans les applications multithread, vous pouvez utiliser
QTimer
dans n'importe quel thread qui a une boucle d'événement. Pour démarrer une boucle d'événements à partir d'un thread non GUI, utilisezQThread::exec()
. Qt utilise l'affinité de thread du temporisateur pour déterminer quel thread émettra le signaltimeout()
. Pour cette raison, vous devez démarrer et arrêter le minuteur dans son thread; il n'est pas possible de démarrer une minuterie à partir d'un autre thread.
En interne, QTimer
utilise simplement le QObject::startTimer
méthode pour tirer après un certain temps. Celui-ci lui-même indique en quelque sorte au thread sur lequel il s'exécute de se déclencher après un certain temps.
Ainsi, votre programme fonctionne correctement en continu et garde une trace des temporisateurs tant que vous ne bloquez pas votre file d'attente d'événements. Si vous craignez que votre temporisateur ne soit pas précis à 100%, essayez de déplacer les rappels de longue durée hors de la file d'attente d'événements dans leur propre thread, ou utilisez une autre file d'attente d'événements pour les temporisateurs.
L'objet QTimer s'enregistre dans EventDispatcher (QAbstractEventDispatcher) qui prend soin d'envoyer des événements de type QTimerEvent chaque fois qu'il y a un délai d'attente pour un QTimer enregistré particulier. Par exemple, sur GNU/Linux, il existe une implémentation privée de QAbstractEventDispatcher appelée QEventDispatcherUNIXPrivate qui effectue des calculs en tenant compte de l'api de la plateforme pour le moment. Les QTimerEvent sont envoyés depuis QEventDispatcherUNIXPrivate dans la file d'attente de la boucle d'événements du même thread auquel appartient l'objet QTimer, c'est-à-dire a été créé.
QEventDispatcherUNIXPrivate ne déclenche pas un événement QTimerEvent en raison d'un événement ou d'une horloge du système d'exploitation, mais parce qu'il vérifie périodiquement le délai d'expiration lorsque processEvents est appelé par la boucle d'événements de thread où réside également QTimer. Voir ici: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qeventdispatcher_unix.cpp.html#_ZN27QEventDispatcherUNIXPrivateC1Ev