简体   繁体   English

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

[英]What happens with Qt signals when the receiver is busy?

In my application, I have an instance of QTimer , whose timeout() signal is connected to a slot in the main window object, causing it to get called periodically. 在我的应用程序中,我有一个QTimer实例,其timeout()信号连接到主窗口对象中的一个插槽,导致它被定期调用。 The slot takes a picture with a camera and saves it to disk. 插槽用相机拍照并将其保存到磁盘。

I was wondering what happens if the signal is emitted (from a separate thread where QTimer executes, I presume) when the receiver (the window object on the main thread) is currently busy (like with taking and saving the previous picture). 我想知道当接收器(主线程上的窗口对象)当前正忙时(如获取并保存前一个图片),如果发出信号(来自QTimer执行的单独线程,我推测)会发生什么。 Does the call get queued and executed after the previous call terminates? 在前一个呼叫终止后,呼叫是否排队并执行? The whole idea is to have it run at regular intervals, but can those calls queue up and then get called randomly when control returns to the event loop, causing a mess? 整个想法是让它定期运行,但这些调用可以排队,然后当控制返回到事件循环时随机调用,导致混乱吗? How can I avoid it? 我怎么能避免呢? Theoretically the slot should execute quickly, but let's say the hardware had some problem and there was a stall. 从理论上讲,插槽应该快速执行,但是假设硬件有一些问题并且存在停顿。

I would like for calls to be dropped rather than queued in such a situation, and even more useful would be the ability to react when it happens (warn user, terminate execution). 我希望在这种情况下调用被丢弃而不是排队,更有用的是能够在发生时作出反应(警告用户,终止执行)。

The other answers at this moment have relevant context. 此时的其他答案都有相关的背景。 But the key thing to know is that if the timer callback is signaling a slot in a different thread, then that connection is either a QueuedConnection or a BlockingQueuedConnection. 但要知道的关键是,如果定时器回调信号通知不同线程中的一个槽,那么该连接可以是QueuedConnection或BlockingQueuedConnection。

So, if you're using the timer to try and get some sort of regular processing done, then this gives you some additional jitter in timing between when the timer fires and when the slot actually executes, as the receiving object is in it's own thread running an independent event loop. 因此,如果您正在使用计时器尝试进行某种常规处理,那么这会在计时器触发和插槽实际执行之间的时间间隔给您一些额外的抖动,因为接收对象在它自己的线程中运行独立的事件循环。 That means it could be doing any number of other tasks when the event is put in the queue and until it finishes processing those events, the picture thread won't execute your timer event. 这意味着当事件放入队列时它可以执行任意数量的其他任务,直到它完成处理这些事件,图片线程将不会执行您的计时器事件。

The timer should be in the same thread as the photo logic. 计时器应与照片逻辑位于同一个线程中。 Putting the timer in the same thread as the camera shot, makes the connection direct, and gives you better stability on your timing intervals. 将计时器放在与摄像机拍摄相同的线程中,可以直接连接,并在定时间隔内提供更好的稳定性。 Especially if the photo capture & save has occasional exceptional durations. 特别是如果照片捕获和保存偶尔会有特殊的持续时间。

It goes something like this, supposing the interval is 10 seconds: 它是这样的,假设间隔是10秒:

  • set timer for 10 seconds 设定计时器10秒钟
  • timer fires 计时器开火
  • save a start time 节省开始时间
  • take photo 拍照
  • save photo to disk (say it takes 3 seconds for some odd reason) 将照片保存到磁盘(因为某些奇怪的原因需要3秒)
  • calculate 10-(current time - start time)= seven seconds 计算10-(当前时间 - 开始时间)= 7秒
  • set time out for seven seconds 设定时间为7秒

You can also setup some logic here to detect skipped intervals (say one of the operations takes 11 seconds to complete... 您还可以在此处设置一些逻辑来检测跳过的间隔(例如,其中一个操作需要11秒才能完成...

I detail here after some experimentation how QTimer behaves when the receiver is busy. 在经过一些实验后,我在这里详细介绍了当接收器忙时QTimer行为。

Here is the experimentation source code: (add QT += testlib to the project file) 这是实验源代码:(将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();
}

When the execution time is smaller than the time interval 当执行时间小于时间间隔时

Then as expected, the timer runs steadily shooting every seconds. 然后如预期的那样,计时器每秒钟稳定地运行。 It does take into account how much time the execution has taken, and then the method timerEvent always starts at a multiple of 1000ms: 它确实考虑了执行所花费的时间,然后方法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 

When only one click is missed because the receiver was busy 因为接收器忙而只丢失一次点击

n[2] = 1500; // small stall (longer than 1sec, but less than 2sec)

Then, the next slot is called right away after the stall is finished, but the subsequent calls are still multiple of 1000ms : 然后,在失速结束后立即调用下一个插槽, 但后续调用仍然是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 

It also works if the following clicks are also missed due to the accumulation of time, as long as there is only one click that is missed at each execution : 如果由于时间的累积而错过了以下点击,它也可以工作, 只要每次执行时只有一次点击错过

n[2] = 1450; // small stall 
n[3] = 1450; // small stall 

output: 输出:

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 

When more than one click is missed because the receiver was very busy 当因为接收器非常繁忙而错过了多次点击时

n[2] = 2500; // big stall (more than 2sec)

If two or more clicks are missed, only then a problem appear . 如果错过了两次或更多次点击,则只会出现问题 The execution times are not synchronized with the first execution, but rather with the moment the stall finished: 执行时间与第一次执行不同步,而是与停顿完成的时刻同步:

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 

Conclusion 结论

The solution of Digikata has to be used if the stalls might be longer than twice the timer interval , but otherwise it is not needed, and the trivial implementation as above works well. 如果停顿可能比定时器间隔的两倍长 ,则必须使用Digikata的解决方案,否则不需要它,并且如上所述的简单实现工作良好。 In the case you'd rather have the following behaviour: 在这种情况下,您宁愿有以下行为:

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 

Then you can still use the trivial implementation, and just check that enteringTime < expectedTime + epsilon . 然后你仍然可以使用简单的实现,只需检查enteringTime < expectedTime + epsilon If it is true, take the picture, if it is false, do nothing. 如果是真的,拍照,如果是假的,什么也不做。

You can use the Qt::(Blocking)QueuedConnection connection type for the connect method to avoid direct connections that shot immediately. 您可以使用Qt::(Blocking)QueuedConnection连接类型来连接方法,以避免立即连接的直接连接。

Since you have separate threads, you should use the blocking version. 由于您有单独的线程,因此应使用阻止版本。 However, you should consider the non-blocking variant when you wish to avoid direct calls without separate thread for the receiver. 但是,如果您希望避免直接调用而没有用于接收器的单独线程,则应考虑非阻塞变体。

Please see the official documentation for details. 有关详细信息,请参阅官方文档

From the documentation for your convenience: 从文档中为方便起见:

Qt::QueuedConnection Qt的:: QueuedConnection

The slot is invoked when control returns to the event loop of the receiver's thread. 当控制返回到接收者线程的事件循环时,将调用该槽。 The slot is executed in the receiver's thread. 插槽在接收器的线程中执行。

Qt::BlockingQueuedConnection Qt的:: BlockingQueuedConnection

Same as QueuedConnection, except the current thread blocks until the slot returns. 与QueuedConnection相同,除了当前线程阻塞直到槽返回。 This connection type should only be used where the emitter and receiver are in different threads. 此连接类型仅应在发射器和接收器位于不同线程中的情况下使用。

What you probably meant to write is that you would not like to have direct connection rather than queued . 您可能想写的是您不希望直接连接而不是排队

QCoreApplication::removePostedEvents ( QObject * receiver, int eventType ) with the event type MetaCall can be used or cleaning up the queue if it is getting saturated with those heavy tasks. 可以使用具有事件类型MetaCall QCoreApplication::removePostedEvents ( QObject * receiver, int eventType ) ,或者如果队列已经满足那些繁重的任务,则清理队列。 Also, you could always use a flag for communicating this with the slot to exit if that is set. 此外,如果已设置,您可以始终使用标志与插槽进行通信以退出。

See the following forum discussion for details, as well: http://qt-project.org/forums/viewthread/11391 有关详细信息,请参阅以下论坛讨论: http//qt-project.org/forums/viewthread/11391

The answer is yes. 答案是肯定的。 When your QTimer and your receiver are in different threads, the call is put in the receivers event queue. 当您的QTimer和您的接收器位于不同的线程中时,该调用将被置于接收器事件队列中。 And if your picture taking or saving routine is hogging execution time, your event can be delayed tremendously. 如果您的拍照或保存程序正在占用执行时间,您的事件可能会被大大延迟。 But this is the same for all events. 但这对所有事件都是一样的。 If a routine does not give control back to the event loop, your gui hangs. 如果例程没有将控制权交还给事件循环,那么你的gui会挂起。 You can use: 您可以使用:

Qt::BlockingQueuedConnection Same as QueuedConnection, except the current thread blocks until the slot returns. Qt :: BlockingQueuedConnection与QueuedConnection相同,除了当前线程阻塞直到槽返回。 This connection type should only be used where the emitter and receiver are in different threads. 此连接类型仅应在发射器和接收器位于不同线程中的情况下使用。

But most likely a situation like this is a hint that something is wrong with your logic. 但是很可能像这样的情况暗示你的逻辑出了问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 问:如果你发出的信号太快会怎么样? - Qt: what happens if you send out signals too quickly? Qt信号和插槽 - 没有任何反应 - Qt Signals and Slots - nothing happens 在 Qt 中,当线程完成时对象会发生什么? - In Qt, what happens to an object when a thread finishes? Qt添加到QGraphicsLinearLayout时QGraphicsItems会发生什么 - Qt what happens to QGraphicsItems when added to QGraphicsLinearLayout Qt:发出信号会导致堆栈溢出(或内存泄漏)吗? 如果他们连接的插槽/线程被阻塞会发生什么? - Qt: Can emitting signals cause an Stack Overflow (or memory leak)? What happens if their connected slot/thread is blocked? Qt中的信号和插槽究竟是什么? - What exactly are signals and slots in Qt? 使用no-angle和-no-opengl编译Qt时会发生什么? - What happens when Qt is compiled with no-angle and -no-opengl? 接收方未发布接收时标准和非阻塞发送会发生什么 - What happens for standard and non-blocking sends when the receiver does not post a receive 在Qt中发送信号时的const-ref - const-ref when sending signals in Qt 当再次触发信号从Qt插槽执行的功能再次被调用时,会发生什么情况? - What happens when a function that is being executed from a Qt Slot is called a second time when the signal is triggered again?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM