Что происходит с сигналами Qt, когда приемник занят?
В моем приложении у меня есть экземпляр QTimer
, сигнал которого timeout()
подключен к слоту в главном объекте окна, заставляя его периодически вызываться. Слот снимает изображение с камеры и сохраняет его на диск.
Мне было интересно, что произойдет, если сигнал испущен (из отдельного потока, где QTimer
выполняется, я полагаю), когда приемник (объект окна в основном потоке) в настоящее время занят (например, при взятии и сохранении предыдущего картина). Вызывается ли вызов в очереди и выполняется после завершения предыдущего вызова? Вся идея состоит в том, чтобы запустить ее через регулярные промежутки времени, но могут ли эти вызовы стоять в очереди, а затем вызываться случайным образом, когда управление возвращается в цикл событий, вызывая беспорядок? Как я могу избежать этого? Теоретически слот должен выполняться быстро, но, скажем, у аппаратного обеспечения возникла какая-то проблема, и там был ларек.
Я бы хотел, чтобы вызовы были удалены, а не поставлены в очередь в такой ситуации, и еще более полезной была бы возможность реагировать, когда это произойдет (предупредить пользователя, прекратить выполнение).
Ответы
Ответ 1
Другие ответы на данный момент имеют соответствующий контекст. Но главное знать, что если обратный вызов таймера сигнализирует слот в другом потоке, то это соединение является либо QueuedConnection, либо BlockingQueuedConnection.
Итак, если вы используете таймер, чтобы попытаться выполнить какую-то обычную обработку, это даст вам дополнительный джиттер во времени между тем, когда срабатывает таймер, и когда слот фактически выполняется, поскольку принимающий объект находится в это собственный поток, выполняющий независимый цикл событий. Это означает, что он может выполнять любое количество других задач, когда событие помещается в очередь и до тех пор, пока оно не завершит обработку этих событий, поток изображений не выполнит ваше событие таймера.
Таймер должен находиться в том же потоке, что и логика фото. Включение таймера в тот же поток, что и камера, делает соединение прямым и обеспечивает лучшую стабильность ваших интервалов времени. Особенно, если захват и сохранение фотографий имеют случайные исключительные продолжительности.
Это происходит примерно так: предположим, что интервал составляет 10 секунд:
-
- установить таймер на 10 секунд
- срабатывает таймер
- сохранить время запуска
- взять фото
- сохранить фото на диск (скажем, требуется 3 секунды по какой-то нечетной причине)
- вычислить 10- (текущее время - время начала) = семь секунд
- установить тайм-аут на семь секунд
Вы также можете установить некоторую логику здесь, чтобы обнаружить пропущенные интервалы (скажем, одна из операций занимает 11 секунд, чтобы завершить...
Ответ 2
Здесь я подробно расскажу о том, как 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
. Если это правда, сделайте снимок, если он ложный, ничего не делает.
Ответ 3
Вы можете использовать тип соединения Qt::(Blocking)QueuedConnection
для метода подключения, чтобы избежать немедленных немедленных прямых подключений.
Поскольку у вас есть отдельные потоки, вы должны использовать блокирующую версию. Тем не менее, вы должны учитывать неблокирующий вариант, когда вы хотите избежать прямых вызовов без отдельного потока для получателя.
Подробнее см. официальную документацию.
Из документации для вашего удобства:
Qt:: QueuedConnection
Слот вызывается, когда управление возвращается к циклу событий потока приемника. Слот выполняется в потоке приемника.
Qt:: BlockingQueuedConnection
То же, что и QueuedConnection, за исключением того, что текущий поток блокируется до тех пор, пока слот не вернется. Этот тип подключения должен использоваться только там, где эмиттер и приемник находятся в разных потоках.
То, что вы, вероятно, хотели написать, это то, что вам не хотелось бы иметь прямое соединение, а не в очереди.
QCoreApplication::removePostedEvents ( QObject * receiver, int eventType )
с типом события MetaCall
можно использовать или очищать очередь, если она насыщается этими тяжелыми задачами. Кроме того, вы всегда можете использовать флаг для связи с этим слотом для выхода, если он установлен.
Подробнее см. в следующем обсуждении форума: http://qt-project.org/forums/viewthread/11391
Ответ 4
Ответ: да. Когда ваш QTimer и ваш ресивер находятся в разных потоках, вызов помещается в очередь событий получателей. И если ваша процедура съемки или сохранения изображений - это время выполнения теста, ваше событие может быть отложено очень сильно. Но это одно и то же для всех событий. Если подпрограмма не возвращает управление циклу событий, ваш gui зависает. Вы можете использовать:
Qt:: BlockingQueuedConnection То же, что и QueuedConnection, кроме текущие блоки потока до тех пор, пока слот не вернется. Этот тип подключения следует использовать только там, где эмиттер и приемник находятся в разных потоки.
Но, скорее всего, подобная ситуация - это намек на то, что с вашей логикой что-то не так.