我正在尝试从不同进程读取输出/日志并在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();
}
该计划目前正按预期运作。但我不确定这是否是正确的解决方案。如果有任何其他/更好/正确的解决方案,请告诉我
谢谢, 苏里亚
答案 0 :(得分:0)
我看到的最引人注目的概念问题是
进程是异步的,无需添加线程来运行它们.¹
您提前关闭了管道:
mService.run();
mStream.pipe().close();
运行不是“阻塞”,因为它不会等待孩子退出。您可以使用wait
来实现这一目标。除此之外,您只需删除close()
来电即可。
使用关闭意味着您将丢失全部或部分输出。如果子进程在输出第一个数据之前需要一段时间,您可能看不到任何输出。
您正在从多个线程访问mStream
而没有同步。这会调用Undefined Behaviour,因为它会打开Data Race。
在这种情况下,您可以通过删除之前提到的mStream.close()
调用来解决当前问题,但是您必须注意 child
之后仅启动阅读器线程已初始化。
严格地说,std::cout
应该采取同样的谨慎态度。
您正在传递io_service
引用,但它未被使用。放弃它似乎是一个好主意。
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
代替asynchronous IO,您甚至可以通过polling来自GUI事件循环内部的服务来取消整个线程。< / p>
如果你要拥有它,并且由于子进程自然地异步执行³你可以这样做:
<强> 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有一个现成的集成(How to integrate Boost.Asio main loop in GUI framework like Qt4 or GTK)
在Boost Process支持的所有平台上³