C++ 当接收器忙时,Qt信号会发生什么情况?

C++ 当接收器忙时,Qt信号会发生什么情况?,c++,multithreading,qt,qt-signals,qtcore,C++,Multithreading,Qt,Qt Signals,Qtcore,在我的应用程序中,我有一个QTimer实例,它的timeout()信号连接到主窗口对象中的一个插槽,导致它定期被调用。插槽使用相机拍摄照片并将其保存到磁盘 我想知道当接收器(主线程上的窗口对象)当前正忙(如拍摄和保存前一张照片)时,如果信号发出(我猜想是从执行QTimer的单独线程发出),会发生什么情况。在前一个调用终止后,调用是否排队并执行?整个想法是让它定期运行,但是当控件返回到事件循环时,这些调用是否会排队,然后被随机调用,从而造成混乱?我怎样才能避免呢?从理论上讲,插槽应该执行得很快,但

在我的应用程序中,我有一个
QTimer
实例,它的
timeout()
信号连接到主窗口对象中的一个插槽,导致它定期被调用。插槽使用相机拍摄照片并将其保存到磁盘

我想知道当接收器(主线程上的窗口对象)当前正忙(如拍摄和保存前一张照片)时,如果信号发出(我猜想是从执行
QTimer
的单独线程发出),会发生什么情况。在前一个调用终止后,调用是否排队并执行?整个想法是让它定期运行,但是当控件返回到事件循环时,这些调用是否会排队,然后被随机调用,从而造成混乱?我怎样才能避免呢?从理论上讲,插槽应该执行得很快,但假设硬件出现了一些问题,出现了一个暂停

在这种情况下,我希望调用被丢弃而不是排队,更有用的是在调用发生时做出反应的能力(警告用户,终止执行)。

您可以使用connect方法的
Qt:(Blocking)QueuedConnection
连接类型来避免立即触发的直接连接

因为您有单独的线程,所以应该使用阻塞版本。但是,当您希望避免直接调用而不需要单独的线程时,应该考虑非阻塞变体。 详情请参阅

为方便起见,请参阅文档:

Qt::QueuedConnection 当控件返回到接收方线程的事件循环时,将调用该插槽。插槽在接收器的线程中执行

Qt::BlockingQueuedConnection 与QueuedConnection相同,除了当前线程阻塞,直到插槽返回。此连接类型仅适用于发射器和接收器位于不同线程中的情况

您可能想写的是,您不希望直接连接而不是排队

QCoreApplication::removePostedEvents(QObject*receiver,int eventType)
可以使用事件类型为
MetaCall
的事件,或者在队列被那些繁重的任务饱和时清理队列。此外,如果设置了退出插槽,则始终可以使用标志与插槽进行通信


有关详细信息,请参见以下论坛讨论:

答案是肯定的。当QTimer和接收方处于不同的线程中时,调用被放入接收方事件队列中。如果您的拍照或保存例程占用了执行时间,那么您的事件可能会被严重延迟。但这对所有事件都是一样的。如果例程没有将控制权交还给事件循环,gui将挂起。您可以使用:

Qt::BlockingQueuedConnection与QueuedConnection相同,除了 当前线程阻塞,直到插槽返回。此连接类型 只能在发射器和接收器处于不同位置时使用 线程


但像这样的情况很可能是暗示你的逻辑有问题。

此时的其他答案都有相关的背景。但需要知道的关键是,如果计时器回调正在向不同线程中的插槽发送信号,那么该连接要么是QueuedConnection,要么是BlockingQueuedConnection

因此,如果您使用计时器尝试完成某种常规处理,那么在计时器启动和插槽实际执行之间,这会给您带来一些额外的时间抖动,因为接收对象在它自己的线程中运行一个独立的事件循环。这意味着当事件被放入队列时,它可以执行任意数量的其他任务,直到它完成处理这些事件,图片线程才会执行计时器事件

计时器应与照片逻辑位于同一线程中。将计时器与摄影机快照放在同一线程中,可以直接连接,并在计时间隔上提供更好的稳定性。特别是如果照片捕获和保存偶尔有特殊的持续时间

假设间隔为10秒,它是这样的:

  • 将计时器设置为10秒
  • 定时器点火
  • 节省开始时间
  • 拍照
  • 将照片保存到磁盘(假设由于某些奇怪的原因需要3秒钟)
  • 计算10-(当前时间-开始时间)=7秒
  • 设定7秒的时间

您还可以在此处设置一些逻辑来检测跳过的间隔(例如,其中一个操作需要11秒才能完成…

在进行了一些实验后,我在此处详细介绍了当接收器忙时,
QTimer
的行为

以下是实验源代码:(将
QT+=testlib
添加到项目文件)

由于接收器忙,仅错过一次单击时

n[2] = 1500; // small stall (longer than 1sec, but less than 2sec)
n[2] = 2500; // big stall (more than 2sec)
然后,在暂停结束后立即调用下一个插槽,但后续调用仍然是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 
输出:

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 
如果错过两次或两次以上的单击,则只会出现问题。执行时间不会与第一次执行同步,而是与暂停完成的时间同步:

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 
结论

如果暂停时间可能超过计时器间隔的两倍,则必须使用的解决方案,但在其他情况下,则不需要此解决方案,并且上述简单的实现效果良好。在这种情况下,您希望具有以下行为:

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
。如果是真的,拍摄图片,如果是假的,什么都不做。

我的直觉反应是考虑将拍摄图片并保存到磁盘的逻辑从主gui线程移动到一个单独的线程中。这应该会使照片线程更容易在常规int上运行
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 
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