QProcess умирает без видимой причины
Кодируя, казалось бы, простую часть приложения Qt, которое будет запускать подпроцесс и читать данные из его стандартного вывода, я натолкнулся на проблему, которая меня действительно озадачила. Приложение должно считывать блоки данных (необработанные видеокадры) из подпроцесса и обрабатывать их по мере их поступления:
- запустить QProcess
- собирать данные пока не хватит на один кадр
- обработать кадр
- вернитесь к шагу 2
Идея заключалась в том, чтобы реализовать цикл обработки с использованием сигналов и слотов - это может показаться глупым в простом урезанном примере, который я привожу ниже, но в рамках исходного приложения показалось вполне разумным. Итак, поехали:
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 - т. Е. Родитель закрыл канал для него. Но я абсолютно не знаю, где и почему это происходит. Кто-нибудь еще?
1 ответ
Код выглядит немного странно (не используя readyRead
сигнал и вместо этого полагаясь на задержанные сигналы / слоты). Как вы указали в обсуждении, вы уже видели ветку на ML-интереса ML, где я спрашивал о подобной проблеме. Я только что понял, что я тоже использовал QueuedConnection
в это время. Я не могу объяснить, почему это неправильно - поставленные в очередь сигналы "должны работать", на мой взгляд. Слепой выстрел в том, что invokeMethod
который используется реализацией Qt, каким-то образом конкурирует с доставкой вашего сигнала, так что вы очищаете буфер чтения до того, как Qt получит возможность обработать данные. Это будет означать, что Qt в конечном итоге будет читать нулевые байты и (правильно) интерпретировать это как EOF
, закрывая трубу.
Я больше не могу найти ссылочную "Qt task 217111", но в их Jira есть пара сообщений о waitForReadyRead
не работает, как ожидают пользователи, см., например, QTBUG-9529.
Я бы отнес это к списку рассылки Qt "проценты" и держаться подальше от waitFor...
семейство методов. Я согласен, что их документация заслуживает обновления.