2

我正在尝试从不同进程读取输出/日志并将它们显示在 GUI 中。这些过程将运行很长时间并产生巨大的输出。我计划流式传输这些流程的输出并根据我的需要显示它们。一直允许我的 gui 应用程序接受用户输入并执行其他操作。

我在这里所做的是,从主线程为每个进程启动两个线程。一个用于启动进程,另一个用于读取进程的输出。

这是我迄今为止提出的解决方案。

// Process Class
class MyProcess {
namespace bp = boost::process;
boost::asio::io_service mService; // member variable of the class
bp::ipstream mStream // member variable of the class
std::thread mProcessThread, mReaderThread // member variables of the class.

public void launch();
};

void
MyProcess::launch()
{
mReaderThread = std::thread([&](){
std::string line;
while(getline(mStream, line)) {
std::cout << line << std::endl;
}
});

mProcessThread = std::thread([&]() {
auto c = boost::child ("/path/of/executable", bp::std_out > mStream, mService);

mService.run();
mStream.pipe().close();
}
}


// Main Gui class
class MyGui
{
MyProcess process;
void launchProcess();
}

MyGui::launchProcess()
{
process.launch();
doSomethingElse();
}

到目前为止,该程序正在按预期工作。但我不确定这是否是正确的解决方案。请让我知道是否有任何替代/更好/正确的解决方案

谢谢,苏里亚

4

1 回答 1

1

我看到的最引人注目的概念问题是

  1. 进程是异步的,无需添加线程来运行它们。¹

  2. 您过早地关闭了管道:

    mService.run();
    mStream.pipe().close();
    

    从不会等待孩子退出的意义上说,运行不是“阻塞”。你可以wait用来实现这一点。除此之外,您可以删除close()呼叫。

    关闭意味着您将丢失全部或部分输出。如果子进程在输出第一个数据之前需要一段时间,您可能看不到任何输出。

  3. 您正在mStream从多个线程访问而不同步。这会调用未定义的行为,因为它会打开数据竞赛

    在这种情况下,您可以通过删除mStream.close()前面提到的调用来消除直接问题,但您必须注意仅在初始化之后才启动读取器线程child

    严格来说,应该对std::cout.

  4. 您正在传递io_service参考,但它没有被使用。只是放弃它似乎是个好主意。

  5. 的析构函数MyProcess需要分离或加入线程。为了防止僵尸,它也需要分离或获取子 pid。

    结合mStream分离阅读器线程的生命周期并不是一个真正的选择,就像mStream从线程中使用的那样。

让我们首先发布第一个修复程序,然后我将建议显示一些在您的示例范围内有意义的更多简化。

首次修复

我使用了一个简单的 bash 命令来模拟一个生成 1000 行的命令ping

Live On Coliru

#include <boost/process.hpp>
#include <thread>
#include <iostream>
namespace bp = boost::process;

/////////////////////////
class MyProcess {
    bp::ipstream mStream;
    bp::child mChild;
    std::thread mReaderThread;

  public:
    ~MyProcess();
    void launch();
};

void MyProcess::launch() {
    mChild = bp::child("/bin/bash", std::vector<std::string> {"-c", "yes ping | head -n 1000" }, bp::std_out > mStream);

    mReaderThread = std::thread([&]() {
        std::string line;
        while (getline(mStream, line)) {
            std::cout << line << std::endl;
        }
    });
}

MyProcess::~MyProcess() {
    if (mReaderThread.joinable()) mReaderThread.join();
    if (mChild.running()) mChild.wait();
}

/////////////////////////
class MyGui {
    MyProcess _process;
  public:
    void launchProcess();
};

void MyGui::launchProcess() {
    _process.launch();
    // doSomethingElse();
}

int main() {
    MyGui gui;
    gui.launchProcess();
}

简化!

在当前模型中,线程不会拉它的重量。

我改为使用io_service异步IO,您甚至可以通过从 GUI 事件循环内部轮询服务来取消整个线程²。

如果您要拥有它,并且由于子进程自然异步执行³,您可以简单地执行以下操作:

Live On Coliru

#include <boost/process.hpp>
#include <thread>
#include <iostream>

std::thread launch(std::string const& command, std::vector<std::string> args = {}) {
    namespace bp = boost::process;

    return std::thread([=] {
        bp::ipstream stream;
        bp::child c(command, args, bp::std_out > stream);

        std::string line;
        while (getline(stream, line)) {
            // TODO likely post to some kind of queue for processing
            std::cout << line << std::endl;
        }

        c.wait(); // reap PID
    });
}

该演示显示与之前完全相同的输出。


¹事实上,添加线程是自找麻烦fork

² 或者可能是空闲的滴答声或类似的想法。Qt 有现成的集成(如何在 Qt4 或 GTK 等 GUI 框架中集成 Boost.Asio 主循环

³ 在 Boost Process 支持的所有平台上

于 2018-05-16T21:18:53.857 回答