2013-04-06 100 views
2

雖然編寫了一個看似簡單的Qt應用程序部分,它將運行一個子進程並從其標準輸出中讀取數據,但我偶然發現了一個讓我感到困惑的問題。該應用程序應改爲從子數據塊(原始視頻幀)並對其進行處理,他們到達:QProcess因無明顯原因而死亡

  1. 開始QProcess中
  2. 收集數據,直到有一幀
  3. 過程框架是不夠
  4. 回到步驟2

當時的想法是使用信號和槽實施加工循環 - 這可能看起來傻的簡單,精簡的例子,我在下面提供,但似乎entirel在原始申請的框架內合理。所以在這裏,我們去:

app::app() { 
    process.start("cat /dev/zero"); 
    buffer = new char[frameLength]; 
    connect(this, SIGNAL(wantNewFrame()), SLOT(readFrame()), Qt::QueuedConnection); 
    connect(this, SIGNAL(frameReady()), SLOT(frameHandler()), Qt::QueuedConnection); 
    emit wantNewFrame(); 
} 

我這裏開始一個微不足道的過程(cat /dev/zero),以便我們可以相信,它不會跑出來的數據。我還建立了兩個連接:一個在需要幀時開始讀取,另一個在幀到達時調用數據處理函數。請注意,這個簡單的示例在單個線程中運行,因此連接被設置爲排隊類型以避免無限遞歸。 wantNewFrame()信號啓動獲取第一幀;它在控制返回到事件循環時被處理。

bool app::readFrame() { 
    qint64 bytesNeeded = frameLength; 
    qint64 bytesRead = 0; 
    char* ptr = buffer; 
    while (bytesNeeded > 0) { 
    process.waitForReadyRead(); 
    bytesRead = process.read(ptr, bytesNeeded); 
    if (bytesRead == -1) { 
     qDebug() << "process state" << process.state(); 
     qDebug() << "process error" << process.error(); 
     qDebug() << "QIODevice error" << process.errorString(); 
     QCoreApplication::quit(); 
     break; 
    } 
    ptr += bytesRead; 
    bytesNeeded -= bytesRead; 
    } 
    if (bytesNeeded == 0) { 
    emit frameReady(); 
    return true; 
    } else 
    return false; 
} 

閱讀框架:基本上,我只是將數據填充到緩衝區,因爲它到達。 frameReady()信號在結束時宣佈幀已準備就緒,並導致數據處理功能運行。

void app::frameHandler() { 
    static qint64 frameno = 0; 
    qDebug() << "frame" << frameno++; 
    emit wantNewFrame(); 
} 

一個簡單的數據處理器:它只是對幀進行計數。完成後,它會發出wantNewFrame()重新開始讀取週期。

這就是它。爲了完整起見,我還會在這裏發佈頭文件和main()。

app.h:

#include <QDebug> 
#include <QCoreApplication> 
#include <QProcess> 

class app : public QObject 
{ 
Q_OBJECT 
public: 
    app(); 
    ~app() { delete[] buffer; } 

signals: 
    void wantNewFrame(); 
    void frameReady(); 

public slots: 
    bool readFrame(); 
    void frameHandler(); 

private: 
    static const quint64 frameLength = 614400; 
    QProcess process; 
    char* buffer; 
}; 

main.cpp中:

#include "app.h" 

int main(int argc, char** argv) 
{ 
    QCoreApplication coreapp(argc, argv); 
    app foo; 
    return coreapp.exec(); 
} 

現在就莫名其妙的一部分。這個程序處理一個隨機數幀的就好了(我見過從十五個有什麼千餘),但最終停止,並抱怨說,QProcess中墜毀:

$ ./app 
frame 1 
... 
frame 245 
frame 246 
frame 247 
process state 0 
process error 1 
QIODevice error "Process crashed" 

處理狀態0表示「未運行」和過程錯誤1意味着「墜毀」。我調查了一下,發現子進程收到一個SIGPIPE - 即父進程關閉了管道。但我有絕對沒有想法在哪裏和爲什麼會發生這種情況。還有其他人嗎?

回答

0

該代碼有點奇怪(不使用readyRead信號,而是依靠延遲信號/插槽)。正如你在討論中指出的那樣,你已經看到了我問過類似問題的thread on the qt-interest ML。我剛剛意識到我當時也使用了QueuedConnection。我無法解釋爲什麼它是錯的 - 在我看來,排隊的信號「應該起作用」。盲目的一點是,Qt實現中使用的invokeMethod以某種方式與您的信號傳遞進行競爭,以便在Qt有機會處理數據之前清空讀取緩衝區。這將意味着Qt最終將讀取零字節並且(正確)將其解釋爲關閉管道的EOF

我找不到引用的「Qt任務217111」了,但是他們的Jira中有幾個關於waitForReadyRead的報告不像用戶期望的那樣工作,參見例如。 QTBUG-9529

我想把它帶到Qt的「興趣」郵件列表中,並保持清除waitFor...方法。我同意他們的文件值得更新。

+0

整潔的建議,但不幸的是我沒有從strace那裏得到很多有用的信息。然而,它確實證實管道確實被關閉。我開始相信我可能沒有做錯任何事情,並且問題是由於Qt的一些內部競爭條件造成的。 – 2013-04-08 22:52:59

+0

這是一個危險的結論 - 作爲一個試圖用Posix系統調用重寫QProcess只是爲了好玩的人,我確實承認它可能是非常誘人的:)。當然,你可以在''strace''的輸出中看到''close()''。如果這還不足以弄清楚爲什麼會發生這種情況,那麼您總是可以在像''gdb''這樣的調試器下運行整個事件,並在''close''中設置一個斷點/ catchpoint。 – 2013-04-09 14:38:18

+0

我在Qt的代碼中找到了一些東西,我確實知道close()被調用的地方。現在......哈哈哈!我正要將一個鏈接粘貼到一個關於Qt-interest的電子郵件線索上,這個線索可能與這個問題有關,並且剛剛認識到它是你開始線程的。 :-)好吧,無論如何,[這裏](http://lists.qt.nokia.com/public/qt-interest/2009-May/006341.html)都是鏈接。我對發生的事情的印象是,主事件循環以某種方式得到關於在管道上等待的新數據的虛假通知,只是發現沒有真正存在的東西。 – 2013-04-10 15:38:27