【问题标题】:How to read stdout/stderr from detached QProcess如何从分离的 QProcess 中读取 stdout/stderr
【发布时间】:2014-03-22 07:39:25
【问题描述】:

我是 Qt 的初学者。我需要从桌面应用程序中的按钮调用命令行程序。该程序下载一个 YouTube 视频。我还需要从中读取标准错误。我写了以下代码:

void YoutubeDL::on_downloadButton_clicked()
{
    [...]

    QProcess p;
    p.startDetached("youtube-dl -f " + get + " " + ui->urlBox->text());
    QString perr = p.readAllStandardError();
    if (perr.length())
        ui->descBox->setText("Error during download.\n" + perr);
    else
        ui->descBox->setText("Download completed!");
}

但是 stderr 读取不会发生。

另一方面,如果我使用非分离的p.start(),然后使用waitForFinished(-1),那么我可以读取标准错误,但 GUI 会冻结,直到下载完成。

如何解决这个问题?

一个相关的问题:我还想要一些能够实时读取下载过程的输出的方法,以便我可以在 GUI 中显示它。 youtube-dl 提供这样的进度报告:

[download]   0.0% of 2.00MiB at 173.22KiB/s ETA 00:12
[download]   0.1% of 2.00MiB at 105.01KiB/s ETA 00:19
[download]   0.3% of 2.00MiB at 96.86KiB/s ETA 00:21
[download]   0.7% of 2.00MiB at 105.23KiB/s ETA 00:19
[download]   1.5% of 2.00MiB at 100.29KiB/s ETA 00:20
...

我希望能够在它们生成时读取它们。

【问题讨论】:

  • 我建议您更多地查找 QThread 而不是运行另一个进程。 QThread 允许您在不冻结 GUI 的情况下进行并行计算。
  • 我确实尝试过研究 QThread,但对于使用它们的最佳方式似乎没有达成共识。我的研究只让我彻底糊涂了。你能指出我正确的方向吗?
  • 你知道什么是线程吗?
  • 是的,我在 C 中使用过 pthreads。
  • 好的,然后在 Youtube 上搜索“voidrealms”。那家伙已经制作了大约 100 个 Qt 教程。它包含 5-6 个关于 QThread 的教程。我强烈建议您将这些视频中的每一个至少观看 3 次。因为有两种使用 QThread 的技术,其中一种是绝对不正确的,但仍然在互联网上流行。

标签: c++ multithreading qt


【解决方案1】:

由于问题的性质,上述所有答案都不正确。 @aakashjain 要求 分离 进程。你们的建议仅在启动的过程仍然附加的情况下有效。

QProcess p
p.startDetached(...)

与

相同
QProcess::startDetached(...)

QProcess::startDetached() 是一个静态方法,它不是任何对象的一部分或返回一个。一旦您调用此方法并成功,该进程将不再以任何方式附加到生成它的进程(您的应用程序)。

我建议您先查看official documentation on this method,然后阅读更多关于interprocess communication 以及如何将一个进程的输出通过管道传输到另一个(在本例中为分离进程到终端)的信息。

我偶然发现了在 PyQt4 中使用 QProcess::startDetached() 进行基本控制的问题,我完全按照@DmitrySokolov 的建议进行操作。令我惊讶的是,状态始终为零,使用 QProcess 非静态方法无法设置或检索任何内容。经过一番挖掘和询问后,我准确地指出了我在回答开头所写的内容 - 如果您使用 QProcess::startDetached(),则必须通过系统工具(例如 kill 命令、管道等),因为您没有可以实际使用的对象。

QProcess::startDetached() 提供了两个重要的返回值:

  • 方法本身的返回值 - 一个布尔值,它告诉您进程的启动是成功 (==true) 还是失败 (==false)
  • 函数的 qint64 * pid 指针参数 - 如果进程已成功启动,pid 将包含分离进程的 PID(进程标识符)

您可以使用此 PID 与进程交互,但您喜欢(当然,进程本身允许您这样做;)) - 您可以终止它、管道输出、使其进入睡眠状态等。

现在回到你的问题。这里有三种解决方案:

  • 启动子进程 - 该进程将包含在 QProcess 对象中,您可以按照@DmitrySokolov 的描述与之交互。但是,这将阻止您处理 UI 的主线程,直到子进程完成其任务并停止(主线程在您的 Qt 应用程序的进程内运行)。我怀疑您是否希望您的 UI 冻结(正如您通过自己的实验所注意到的那样),尤其是因为我们正在讨论下载可能大量的视频数据内容...
  • 启动一个分离的进程 - 我在这个解决方案列表之前已经描述过。然而,它需要对进程间通信有更多的了解。如果您愿意并想花一些时间研究这个主题(我只能鼓励您这样做!),那就去做吧
  • 在其中启动一个线程和一个子进程 - 您不仅可以非常巧妙地控制下载任务,还可以通过槽和信号向 UI 提供出色的反馈,这将改善用户体验很多。这是此类任务最广泛使用的方式,我还可以在您的评论中看到您希望在ui->descBox 中输出进度(您还可以添加 QProgressbar 以使输出更加用户友好并执行 stdout/stderr背景中的东西仅供您查看)。老实说,我没有对此进行测试,但它应该可以工作。

【讨论】:

    【解决方案2】:

    在启动进程之前,将QProcess 信号连接到您的插槽:

    void finished(int exitCode, QProcess::ExitStatus exitStatus)
    void readyReadStandardError()
    void readyReadStandardOutput()
    

    当finished() 信号被触发时,您可以使用readAllStandardError() 方法读取所有sdterr 输出,或者您可以在readyReadStandardError() 被触发时读取stderr 数据的一部分。

    据我了解,youtube-dl 将进度数据输出到stdout。所以当readyReadStandardOutput信号被触发时,你可以读取并解析。

    更新

    class YoutubeDL
    {
    ...
    private:
        QProcess p;
        void on_process_finished(int exitCode, QProcess::ExitStatus exitStatus);
        void on_process_readyReadStandardOutput();
    };
    
    void YoutubeDL::on_downloadButton_clicked()
    {
        ...
    
        p.connect(&p, &QProcess::readyReadStandardOutput,
                  this, &YoutubeDL::on_process_readyReadStandardOutput);
        p.connect(&p, (void (QProcess::*)(int,QProcess::ExitStatus))&QProcess::finished,
                  this, &YoutubeDL::on_process_finished);
    
        p.start("youtube-dl -f " + get + " " + ui->urlBox->text());
    }
    
    void YoutubeDL::on_process_finished(int exitCode, QProcess::ExitStatus exitStatus)
    {
        QString perr = p.readAllStandardError();
        if (perr.length())
            ui->descBox->setText("Error during download.\n" + perr);
        else
            ui->descBox->setText("Download completed!");
    }
    
    void YoutubeDL::on_process_readyReadStandardOutput()
    {
        p.setReadChannel(QProcess::StandardOutput);
        QTextStream stream(&p);
        while (!stream.atEnd()) {
            QString line = stream.readLine();
            // extract progress info from line and etc.
            ...
        }
    }
    

    【讨论】:

      【解决方案3】:

      不要等待完成。而不是使用 QProcess 信号 readyReadStandardError 来通知您可以读取任何内容。

      【讨论】:

      • 好的,我将如何使用它来阅读进度报告并将其写入ui->descBox?
      • 你得到stderr准备好的信号,然后你使用readAllStandardError读取它
      猜你喜欢
      • 2021-12-04
      • 2011-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-13
      • 2013-05-30
      • 2016-12-21
      • 1970-01-01
      相关资源
      最近更新 更多