webentwicklung-frage-antwort-db.com.de

Läuft ein QTimer-Objekt in einem separaten Thread? Was ist ihr Mechanismus?

Wenn ich in Qt 5 ein QTimer -Objekt erstelle und es mit der Memberfunktion start() starte, wird ein separater Thread erstellt, der die Zeit verfolgt und die Funktion timeout() aufruft in regelmäßigen Abständen?

Beispielsweise,

QTimer *timer = new QTimer;
timer->start(10);
connect(timer,SIGNAL(timeout()),someObject,SLOT(someFunction()));

Woher weiß das Programm, wann timeout() auftritt? Ich denke, es müsste in einem separaten Thread ausgeführt werden, da ich nicht sehe, wie ein sequentielles Programm die Zeit verfolgen und seine Ausführung gleichzeitig fortsetzen kann. Es ist mir jedoch nicht gelungen, diesbezügliche Informationen in der Qt-Dokumentation oder anderswo zu finden, um dies zu bestätigen.

Ich habe die offizielle Dokumentation gelesen und bestimmte Fragen zu StackOverflow wie das und das scheinen sehr verwandt zu sein, aber ich konnte meine Antwort nicht durchbringen Sie.

Kann jemand den Mechanismus erklären, mit dem ein QTimer -Objekt funktioniert?

Bei der weiteren Suche stellte ich fest, dass laut diese Antwort von Bill erwähnt wird, dass

Ereignisse werden vom Betriebssystem asynchron übermittelt. Daher scheint etwas anderes vor sich zu gehen. Es gibt, aber nicht in Ihrem Programm.

Bedeutet dies, dass timeout() vom Betriebssystem verarbeitet wird? Gibt es Hardware, die die Zeit überwacht und Interrupts in angemessenen Abständen sendet? Wenn dies jedoch der Fall ist, können so viele Timer gleichzeitig und unabhängig voneinander ausgeführt werden, wie kann jeder Timer separat verfolgt werden?

Was ist der Mechanismus?

Vielen Dank.

24
GoodDeeds

Wenn ich in Qt 5 ein QTimer-Objekt erstelle und es mit der Elementfunktion start () starte, wird ein separater Thread erstellt, der die Zeit protokolliert und die Funktion timeout () in regelmäßigen Abständen aufruft.

Nein; Das Erstellen eines separaten Threads wäre teuer und nicht erforderlich, sodass QTimer nicht so implementiert wird.

Woher weiß das Programm, wann timeout () auftritt?

Die QTimer :: start () -Methode kann eine Systemzeitfunktion (z. B. gettimeofday () oder ähnliches) aufrufen, um (innerhalb weniger Millisekunden) herauszufinden, zu welchem ​​Zeitpunkt start () aufgerufen wurde. Es kann dann zehn Millisekunden (oder einen beliebigen Wert, den Sie angegeben haben) zu dieser Zeit addieren, und jetzt enthält es einen Datensatz, der angibt, wann das Signal timeout () als nächstes ausgegeben werden soll.

Wenn Sie diese Informationen haben, was bedeutet es dann, um sicherzustellen, dass dies geschieht?

Die wichtigste Tatsache ist, dass die QTimer-Timeout-Signalemission nur funktioniert, wenn Ihr Qt-Programm in der Qt-Ereignisschleife ausgeführt wird. Fast jedes Qt-Programm hat so etwas, normalerweise in der Nähe des unteren Randes seine main () -Funktion:

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

Beachten Sie, dass in einer typischen Anwendung fast die gesamte Zeit der Anwendung in diesem exec () -Aufruf verbracht wird. Das heißt, der Aufruf von app.exec () wird erst dann zurückgegeben , wenn die Anwendung beendet wird.

Was ist also in diesem exec () -Aufruf los, während Ihr Programm ausgeführt wird? Bei einer großen komplexen Bibliothek wie Qt ist es notwendigerweise kompliziert, aber es ist keine allzu große Vereinfachung zu sagen, dass eine Ereignisschleife ausgeführt wird, die konzeptionell ungefähr so ​​aussieht:

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

Wenn Ihre App inaktiv ist, wird der Prozess innerhalb des Aufrufs SleepUntilThereIsSomethingToDo () in den Ruhezustand versetzt, aber sobald ein Ereignis eintrifft, das behandelt werden muss (z. B. der Benutzer bewegt die Maus oder drückt eine Taste, oder Daten kommen auf einem Socket an , oder etc), SleepUntilThereIsSomethingToDo () wird zurückgegeben und dann wird der Code zum Reagieren auf dieses Ereignis ausgeführt, was dazu führt, dass die entsprechende Aktion wie das Aktualisieren der Widgets oder das Signal timeout () aufgerufen wird.

Woher weiß SleepUntilThereIsSomethingToDo (), wann es Zeit ist, aufzuwachen und zurückzukehren? Dies hängt stark von dem Betriebssystem ab, auf dem Sie ausgeführt werden, da verschiedene Betriebssysteme unterschiedliche APIs für die Verarbeitung dieser Art von Dingen haben. Eine klassische UNIX-Methode zur Implementierung einer solchen Funktion ist jedoch POSIX select (). anrufen:

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

Beachten Sie, dass select () drei verschiedene fd_set-Argumente akzeptiert, von denen jedes eine Reihe von Dateideskriptoren angeben kann. Indem Sie die entsprechenden fd_set-Objekte an diese Argumente übergeben, können Sie select () veranlassen, in dem Moment aufzuwachen, in dem eine E/A-Operation für einen der Dateideskriptoren möglich wird, die Sie überwachen möchten, damit Ihr Programm die folgenden Aktionen ausführen kann I/O ohne Verzögerung. Der interessante Teil für uns ist jedoch das letzte Argument, das ein Timeout-Argument ist. Insbesondere können Sie ein struct timeval Objekt hier, das zu select () sagt: "Wenn nach (so vielen) Mikrosekunden keine E/A-Ereignisse aufgetreten sind, sollten Sie einfach aufgeben und trotzdem zurückkehren".

Dies erweist sich als sehr nützlich, da die Funktion SleepUntilThereIsSomethingToDo () mit diesem Parameter ungefähr Folgendes ausführen kann (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 ...]
}   

Hoffentlich macht das Sinn, und Sie können sehen, wie die Ereignisschleife das timeout-Argument von select () verwendet, um das timeout () - Signal (ungefähr) zu der Zeit aufzuwecken und auszugeben, die es zuvor berechnet hatte, als Sie start (aufgerufen haben. ).

Übrigens, wenn in der App mehr als ein QTimer gleichzeitig aktiv ist, ist das kein Problem. In diesem Fall muss SleepUntilThereIsSomethingToDo () nur alle aktiven QTimer durchlaufen, um denjenigen mit dem kleinsten Zeitstempel für das nächste Zeitlimit zu finden, und nur diesen minimalen Zeitstempel für die Berechnung des maximalen Zeitintervalls verwenden, das select () verwendet. sollte schlafen dürfen. Nachdem select () zurückgegeben wurde, durchläuft DoTheThingsThatNeedDoingNow () auch die aktiven Timer und gibt nur für diejenigen ein Timeout-Signal aus, deren nächster Timeout-Zeitstempel nicht größer als die aktuelle Zeit ist. Die Ereignisschleife wird wiederholt (so schnell oder so langsam wie nötig), um einen Anschein von Multithread-Verhalten zu erwecken, ohne dass tatsächlich mehrere Threads erforderlich sind.

26
Jeremy Friesner

Ein Blick auf Dokumentation zu Timern und Quellcode von QTimer und QObject zeigt, dass der Timer im Thread ausgeführt wird/event-Schleife, die dem Objekt zugewiesen ist. Aus dem Dokument:

Damit QTimer funktioniert, muss Ihre Anwendung eine Ereignisschleife enthalten. Das heißt, Sie müssen QCoreApplication::exec() irgendwo aufrufen. Timer-Ereignisse werden nur geliefert, während die Ereignisschleife ausgeführt wird.

In Multithread-Anwendungen können Sie QTimer in jedem Thread mit einer Ereignisschleife verwenden. Verwenden Sie QThread::exec(), um eine Ereignisschleife von einem Nicht-GUI-Thread aus zu starten. Qt verwendet die Thread-Affinität des Timers, um zu bestimmen, welcher Thread das Signal timeout() ausgibt. Aus diesem Grund müssen Sie den Timer in seinem Thread starten und stoppen. Es ist nicht möglich, einen Timer von einem anderen Thread aus zu starten.

Intern verwendet QTimer einfach das QObject::startTimer Methode, um nach einer bestimmten Zeit zu feuern. Dieser selbst sagt irgendwie, dass der Thread, auf dem er läuft, nach einiger Zeit feuern soll.

So läuft Ihr Programm ohne Unterbrechung einwandfrei und verfolgt die Timer, solange Sie Ihre Ereigniswarteschlange nicht blockieren. Wenn Sie befürchten, dass Ihr Zeitgeber nicht 100% genau ist, versuchen Sie, lang laufende Rückrufe in einem eigenen Thread aus der Ereigniswarteschlange zu verschieben, oder verwenden Sie eine andere Ereigniswarteschlange für die Zeitgeber.

4
msrd0

Das QTimer-Objekt registriert sich selbst im EventDispatcher (QAbstractEventDispatcher), der jedes Mal, wenn ein Timeout für einen bestimmten registrierten QTimer auftritt, Ereignisse vom Typ QTimerEvent sendet. Unter GNU/Linux gibt es beispielsweise eine private Implementierung von QAbstractEventDispatcher namens QEventDispatcherUNIXPrivate, die Berechnungen unter Berücksichtigung der Plattform-API für die Zeit vornimmt. Die QTimerEvent werden von QEventDispatcherUNIXPrivate in die Warteschlange der Ereignisschleife desselben Threads gesendet, zu dem das QTimer-Objekt gehört, d. H. Erstellt wurde.

QEventDispatcherUNIXPrivate löst ein QTimerEvent nicht aufgrund eines Betriebssystemereignisses oder einer Systemuhr aus, sondern weil es regelmäßig das Zeitlimit überprüft, wenn processEvents von der Thread-Ereignisschleife aufgerufen wird, in der sich auch QTimer befindet. Siehe hier: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qeventdispatcher_unix.cpp.html#_ZN27QEventDispatcherUNIXPrivateC1Ev

2
Iurie Nistor