QProcess 无缘无故死亡

Posted

技术标签:

【中文标题】QProcess 无缘无故死亡【英文标题】:QProcess dies for no obvious reason 【发布时间】:2013-04-06 01:48:11 【问题描述】:

在编写 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();

现在是奇怪的部分。这个程序可以很好地处理随机数量的帧(我见过从 15 到 1000 多个),但最终停止并抱怨 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 信号,而是依赖延迟的信号/插槽)。正如您在讨论中指出的那样,您已经看到了thread on the qt-interest ML,我在其中询问了类似的问题。我刚刚意识到当时我也使用了QueuedConnection。我无法解释为什么它是错误的——在我看来,排队的信号“应该起作用”。一个盲注是 Qt 的实现使用的 invokeMethod 以某种方式与您的信号传递竞争,以便您在 Qt 有机会处理数据之前清空读取缓冲区。这意味着 Qt 最终将读取零字节并(正确地)将其解释为 EOF,从而关闭管道。

我再也找不到引用的“Qt 任务 217111”,但是在他们的 Jira 中有几个关于 waitForReadyRead 没有按用户预期工作的报告,请参阅例如QTBUG-9529.

我会把它带到 Qt 的“兴趣”邮件列表中,并远离waitFor... 系列方法。我同意他们的文档值得更新。

【讨论】:

不错的建议,但不幸的是,我没有从 strace 获得太多有用的信息。然而,它确实证实了管道确实正在关闭。我开始相信我可能没有做错任何事情,并且问题的发生是由于 Qt 的一些内部竞争条件。 这是一个危险的结论——作为一个为了好玩而试图用 Posix 系统调用重写 QProcess 的人,我承认它可能非常诱人:)。当然,您可以在strace 的输出中看到close()。如果这还不足以弄清楚为什么会发生这种情况,您可以随时在像 gdb 这样的调试器下运行整个事情,并在 close 中设置断点/捕获点。 我对 Qt 的代码进行了一些研究,但确实找到了调用 close() 的位置。现在……哈哈哈!我正准备在 Qt-interest 上粘贴一个可能与此问题有关的电子邮件线程的链接,并且刚刚意识到是您发起了该线程。 :-) 好吧,here 无论如何都是链接。我对正在发生的事情的印象是,主事件循环以某种方式收到有关管道中等待新数据的虚假通知,结果却发现那里实际上什么都没有。 啊 :),现在肯定敲响了警钟——谢谢你提醒我。我的代码中的问题是(正如蒂亚戈在该线程的其他回复中指出的那样)我实际上是从附加到readyRead 信号的插槽中调用waitForReadyRead(阅读链接的 Qt 错误跟踪器了解详细信息;他们有特殊的解决方法在他们的一些课程中就是为了这个)。我建议更改您的代码,以便它改为侦听QProcess::readyRead 信号并检查bytesAvailable,如果没有足够的数据则尽早返回并依靠重新发出readyRead 信号进行处理。 我已经考虑过按照您的建议重写代码以收听QProcess::readyRead,但我首先想知道当前实现的问题所在。 “这行不通,让我们尝试另一种方式”的动机对我来说似乎不够好。我希望 Qt 文档在某处说“你不应该这样做,否则会发生坏事”,而我不知何故忽略了它。但是我还没有找到这样的解释。这就是为什么我说这对我来说似乎是一个错误。我只是不觉得我在做任何我不应该做的事情。

以上是关于QProcess 无缘无故死亡的主要内容,如果未能解决你的问题,请参考以下文章

在 Qt 中获取输出:'QProcess::start()' 和 'QProcess:readAllStandardOutPut()'

如何在 QProcess 执行它们时打印真正的 QProcess 参数列表

监控正在运行的 qprocess 并在 qprocess 完成时返回值

避免 QProcess 被杀死 (QProcess: Destroyed while process is still running)

QProcess异常QT

QProcess.start 不返回退出状态或退出代码