簡體   English   中英

當接收器忙時,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 如果是真的,拍照,如果是假的,什么也不做。

您可以使用Qt::(Blocking)QueuedConnection連接類型來連接方法,以避免立即連接的直接連接。

由於您有單獨的線程,因此應使用阻止版本。 但是,如果您希望避免直接調用而沒有用於接收器的單獨線程,則應考慮非阻塞變體。

有關詳細信息,請參閱官方文檔

從文檔中為方便起見:

Qt的:: QueuedConnection

當控制返回到接收者線程的事件循環時,將調用該槽。 插槽在接收器的線程中執行。

Qt的:: BlockingQueuedConnection

與QueuedConnection相同,除了當前線程阻塞直到槽返回。 此連接類型僅應在發射器和接收器位於不同線程中的情況下使用。

您可能想寫的是您不希望直接連接而不是排隊

可以使用具有事件類型MetaCall QCoreApplication::removePostedEvents ( QObject * receiver, int eventType ) ,或者如果隊列已經滿足那些繁重的任務,則清理隊列。 此外,如果已設置,您可以始終使用標志與插槽進行通信以退出。

有關詳細信息,請參閱以下論壇討論: http//qt-project.org/forums/viewthread/11391

答案是肯定的。 當您的QTimer和您的接收器位於不同的線程中時,該調用將被置於接收器事件隊列中。 如果您的拍照或保存程序正在占用執行時間,您的事件可能會被大大延遲。 但這對所有事件都是一樣的。 如果例程沒有將控制權交還給事件循環,那么你的gui會掛起。 您可以使用:

Qt :: BlockingQueuedConnection與QueuedConnection相同,除了當前線程阻塞直到槽返回。 此連接類型僅應在發射器和接收器位於不同線程中的情況下使用。

但是很可能像這樣的情況暗示你的邏輯出了問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM