当接收器繁忙时,Qt信号会发生什么
What happens with Qt signals when the receiver is busy?
在我的应用程序中,我有一个QTimer
的实例,其timeout()
信号连接到主窗口对象中的一个插槽,导致它被定期调用。插槽使用相机拍摄照片并将其保存到磁盘。
想知道当接收器(主线程上的窗口对象)当前繁忙(例如拍摄和保存上一张图片)时发出信号(我假设从执行QTimer
的单独线程)会发生什么。呼叫是否会在上一个呼叫终止后排队并执行?整个想法是让它定期运行,但是这些调用是否可以排队,然后在控制返回到事件循环时随机调用,从而导致混乱?我怎样才能避免它?从理论上讲,插槽应该快速执行,但是假设硬件出现问题并且出现停滞。
我希望在这种情况下丢弃呼叫而不是排队,更有用的是在发生时做出反应的能力(警告用户,终止执行)。
此时的其他答案具有相关的上下文。但要知道的关键是,如果计时器回调正在向不同线程中的插槽发出信号,则该连接要么是 QueuedConnection 要么是 BlockingQueuedConnection。
因此,如果您使用计时器来尝试完成某种常规处理,那么这会在计时器触发和插槽实际执行之间提供一些额外的时序抖动,因为接收对象在它自己的线程中运行独立的事件循环。这意味着当事件放入队列时,它可以执行任意数量的其他任务,并且在完成处理这些事件之前,图片线程不会执行计时器事件。
计时器应与照片逻辑位于同一线程中。将计时器放在与相机拍摄相同的线程中,可以使连接直接,并为您提供更好的计时间隔稳定性。特别是如果照片捕获和保存偶尔有异常持续时间。
它是这样的,假设间隔是 10 秒:
- 将计时器设置为 10 秒
- 计时器触发
- 保存开始时间
- 拍照
- 将照片保存到磁盘(假设由于某种奇怪的原因需要 3 秒)
- 计算 10-(当前时间 - 开始时间)= 7 秒
- 将超时设置为 7 秒
您还可以在此处设置一些逻辑来检测跳过的间隔(假设其中一个操作需要 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
始终以 1000ms 的倍数开始:
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)
然后,在停止完成后立即调用下一个插槽,但后续调用仍然是 1000ms 的倍数:
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 和接收方位于不同的线程中时,调用将放入接收方事件队列中。而且,如果您的拍照或保存例程占用了执行时间,您的活动可能会大大延迟。但这对所有事件都是一样的。如果例程没有将控制权交还给事件循环,则您的 gui 将挂起。您可以使用:
Qt::BlockingQueuedConnection 与 QueuedConnection 相同,除了 当前线程阻塞,直到插槽返回。此连接类型 应仅在发射器和接收器位于不同位置时使用 线程。
但最有可能的是,这样的情况暗示你的逻辑有问题。
连接方法使用 Qt::(Blocking)QueuedConnection
连接类型,以避免立即触发的直接连接。
由于您有单独的线程,因此应使用阻塞版本。但是,当您希望避免在没有接收器单独线程的情况下直接调用时,应考虑非阻塞变体。
有关详细信息,请参阅官方文档。
为方便起见,请从文档中获取:
Qt::QueuedConnection
当控制返回到接收器线程的事件循环时,将调用该槽。该插槽在接收器的线程中执行。
Qt::阻塞排队连接
与 QueuedConnection 相同,只是当前线程阻塞,直到插槽返回。此连接类型只应在发射器和接收器位于不同线程中时使用。
您可能想写的是,您不希望直接连接而不是排队。
可以使用事件类型的QCoreApplication::removePostedEvents ( QObject * receiver, int eventType )
MetaCall
,或者在队列被这些繁重的任务饱和时清理队列。此外,如果设置了,您始终可以使用标志将其与插槽进行通信以退出。
有关详细信息,请参阅以下论坛讨论:http://qt-project.org/forums/viewthread/11391
- 没有信号处理程序的POSIX定时器的目的是什么?
- 当再次触发信号时,从Qt插槽执行的功能被第二次调用时会发生什么?
- 我应该使用什么信号来终止/终止Windows上的应用程序
- 使用信号检测子进程何时终止的最佳方法是什么?
- 当我们关闭QT中的窗口时,发出的信号是什么
- 对于属性上的 NOTIFY 信号,如果我给它一个参数有什么区别?
- 括号内括号内的含义是什么,以获得增强信号
- 什么时候会有QNetworkReply::error信号后跟一个finish()信号
- 信号和插槽的效用是什么
- netbeans C++调试中的信号是什么
- 在发出等待条件变量的信号后,线程何时获取锁?是什么决定了它
- 如果我们不等待就发出信号量会发生什么
- pthread_join() 可能导致什么信号
- Qt udp套接字,什么会触发套接字的readyRead信号?
- 当接收器繁忙时,Qt信号会发生什么
- 问:如果你发送信号太快会发生什么
- 什么是通过外部信号停止 std::thread 的有效方法
- 在更改Gtk::Entry的文本之前,我应该捕捉什么信号来获取它
- 使用套接字,发出通信结束信号的最佳做法是什么
- 我不希望一个作为守护进程的服务器进程关闭——即使在收到终止信号时也是如此.有什么办法可以确保这一点吗