В моем приложении у меня есть экземпляр QTimer
, чья timeout()
Сигнал подключается к слоту в объекте главного окна, вызывая его периодический вызов. Слот делает снимок с камеры и сохраняет его на диск.
Мне было интересно, что произойдет, если сигнал испускается (из отдельного потока, где QTimer
выполняется, я полагаю), когда получатель (объект окна в главном потоке) в данный момент занят (как при съемке и сохранении предыдущего изображения). Будет ли вызов поставлен в очередь и выполнен после завершения предыдущего вызова? Вся идея состоит в том, чтобы он выполнялся через регулярные промежутки времени, но могут ли эти вызовы встать в очередь и затем вызываться случайным образом, когда управление возвращается в цикл обработки событий, что вызывает беспорядок? Как я могу избежать этого? Теоретически слот должен работать быстро, но, скажем, у оборудования были некоторые проблемы и возникла ошибка.
Я хотел бы, чтобы вызовы отбрасывались, а не ставились в очередь в такой ситуации, и еще более полезной была бы возможность реагировать, когда это происходит (предупредить пользователя, прекратить выполнение).
Другие ответы на данный момент имеют соответствующий контекст. Но главное, что нужно знать, это то, что если обратный вызов таймера сигнализирует о слоте в другом потоке, то это соединение является либо QueuedConnection, либо BlockingQueuedConnection.
Итак, если вы используете таймер, чтобы попытаться выполнить некоторую регулярную обработку, то это дает вам дополнительный джиттер во времени между моментом срабатывания таймера и фактическим выполнением слота, так как принимающий объект находится в своем собственном потоке. запуск независимого цикла событий. Это означает, что он может выполнять любое количество других задач, когда событие помещается в очередь, и пока оно не завершит обработку этих событий, поток изображений не будет выполнять ваше событие таймера.
Таймер должен быть в том же потоке, что и фото логика. Помещение таймера в ту же нить, что и снимок с камеры, обеспечивает прямое соединение и повышает стабильность ваших временных интервалов. Особенно если фото захватить & время сохранения может быть исключительным.
Это выглядит примерно так, предполагая, что интервал составляет 10 секунд:
Здесь вы также можете настроить некоторую логику для обнаружения пропущенных интервалов (скажем, для выполнения одной из операций требуется 11 секунд …
Я подробно здесь после некоторых экспериментов, как QTimer
ведет себя, когда приемник занят.
Вот исходный код эксперимента: (добавить QT += testlib
в файл проекта)
#include <QtGui>
#include <QtDebug>
#include <QTest>
struct MyWidget: public QWidget
{
QList<int> n; // n[i] controls how much time the i-th execution takes
QElapsedTimer t; // measure how much time has past since we launch the app
MyWidget()
{
// The normal execution time is 200ms
for(int k=0; k<100; k++) n << 200;
// Manually add stalls to see how it behaves
n[2] = 900; // stall less than the timer interval
// Start the elapsed timer and set a 1-sec timer
t.start();
startTimer(1000); // set a 1-sec timer
}
void timerEvent(QTimerEvent *)
{
static int i = 0; i++;
qDebug() << "entering:" << t.elapsed();
qDebug() << "sleeping:" << n[i]; QTest::qSleep(n[i]);
qDebug() << "leaving: " << t.elapsed() << "\n";
}
};
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
MyWidget w;
w.show();
return app.exec();
}
Когда время выполнения меньше интервала времени
Затем, как и ожидалось, таймер работает стабильно, снимая каждую секунду. Он учитывает, сколько времени заняло выполнение, а затем метод timerEvent
всегда начинается с кратного 1000 мс:
entering: 1000
sleeping: 200
leaving: 1201
entering: 2000
sleeping: 900
leaving: 2901
entering: 3000
sleeping: 200
leaving: 3201
entering: 4000
sleeping: 200
leaving: 4201
Когда пропущен только один щелчок, потому что получатель был занят
n[2] = 1500; // small stall (longer than 1sec, but less than 2sec)
Затем следующий слот вызывается сразу после того, как стойло закончено, но последующие вызовы по-прежнему кратны 1000 мс:
entering: 1000
sleeping: 200
leaving: 1200
entering: 2000
sleeping: 1500
leaving: 3500 // one timer click is missed (3500 > 3000)
entering: 3500 // hence, the following execution happens right away
sleeping: 200
leaving: 3700 // no timer click is missed (3700 < 4000)
entering: 4000 // normal execution times can resume
sleeping: 200
leaving: 4200
entering: 5000
sleeping: 200
leaving: 5200
Это также работает, если следующие клики также пропущены из-за накопления времени, до тех пор, пока при каждом выполнении пропускается только один клик:
n[2] = 1450; // small stall
n[3] = 1450; // small stall
выход:
entering: 1000
sleeping: 200
leaving: 1201
entering: 2000
sleeping: 1450
leaving: 3451 // one timer click is missed (3451 > 3000)
entering: 3451 // hence, the following execution happens right away
sleeping: 1450
leaving: 4901 // one timer click is missed (4901 > 4000)
entering: 4902 // hence, the following execution happens right away
sleeping: 200
leaving: 5101 // one timer click is missed (5101 > 5000)
entering: 5101 // hence, the following execution happens right away
sleeping: 200
leaving: 5302 // no timer click is missed (5302 < 6000)
entering: 6000 // normal execution times can resume
sleeping: 200
leaving: 6201
entering: 7000
sleeping: 200
leaving: 7201
Когда пропущено более одного клика, потому что получатель был очень занят
n[2] = 2500; // big stall (more than 2sec)
Если пропущены два или более кликов, только тогда возникает проблема. Время выполнения не синхронизируется с первым выполнением, а скорее с моментом остановки:
entering: 1000
sleeping: 200
leaving: 1200
entering: 2000
sleeping: 2500
leaving: 4500 // two timer clicks are missed (3000 and 4000)
entering: 4500 // hence, the following execution happens right away
sleeping: 200
leaving: 4701
entering: 5500 // and further execution are also affected...
sleeping: 200
leaving: 5702
entering: 6501
sleeping: 200
leaving: 6702
Заключение
Решение Digikata должен быть использован если киоски могут быть длиннее, чем в два раза интервал таймера, но в остальном это не нужно, и тривиальная реализация, как указано выше, работает хорошо. В случае, если вы предпочитаете следующее поведение:
entering: 1000
sleeping: 200
leaving: 1200
entering: 2000
sleeping: 1500
leaving: 3500 // one timer click is missed
entering: 4000 // I don't want t execute the 3th execution
sleeping: 200
leaving: 4200
Тогда вы все еще можете использовать тривиальную реализацию, и просто проверить, что enteringTime < expectedTime + epsilon
, Если это правда, сделайте снимок, если это ложь, ничего не делайте.
Ответ — да. Когда ваш QTimer и ваш получатель находятся в разных потоках, вызов помещается в очередь событий получателей. И если ваша рутина съемки или сохранения затрачивает время выполнения, ваше событие может быть значительно отложено. Но это одинаково для всех событий. Если подпрограмма не возвращает управление циклу событий, ваш графический интерфейс зависает. Ты можешь использовать:
Qt :: BlockingQueuedConnection То же, что QueuedConnection, кроме
текущий поток блокируется, пока слот не вернется. Этот тип подключения
следует использовать только тогда, когда излучатель и приемник находятся в разных
потоки.
Но, скорее всего, такая ситуация — намек на то, что с вашей логикой что-то не так.
Вы можете использовать Qt::(Blocking)QueuedConnection
тип соединения для метода соединения, чтобы избежать непосредственных соединений, которые разрываются немедленно.
Поскольку у вас есть отдельные потоки, вы должны использовать блокирующую версию. Однако вы должны рассмотреть вариант неблокирования, когда вы хотите избежать прямых вызовов без отдельного потока для получателя.
Пожалуйста, смотрите официальная документация для деталей.
Из документации для вашего удобства:
Слот вызывается, когда управление возвращается в цикл обработки событий получателя. Слот выполняется в потоке получателя.
То же, что QueuedConnection, за исключением блоков текущего потока, пока слот не вернется. Этот тип соединения должен использоваться только тогда, когда излучатель и приемник находятся в разных потоках.
Вы, вероятно, хотели написать, что вы не хотели бы иметь непосредственный соединение, а не очередь.
QCoreApplication::removePostedEvents ( QObject * receiver, int eventType )
с типом события MetaCall
можно использовать или очистить очередь, если она насыщается этими тяжелыми задачами. Кроме того, вы всегда можете использовать флаг для связи с этим слотом для выхода, если он установлен.
Подробности смотрите также в следующем обсуждении на форуме: http://qt-project.org/forums/viewthread/11391