{boost :: asio :: io_context`与`boost :: process :: async_pipe`:有没有办法可靠地运行它?

时间:2019-07-11 13:02:12

标签: c++ asynchronous boost boost-asio boost-process

使用Windows API,我们可以使用管道转发进程输出和错误流,因此我们可以读取进程输出而无需任何临时文件。代替这个:

std::system("my_command.exe > out.tmp");

我们可以更快地工作,并且没有风险来生成很多被遗忘的临时文件(例如在系统崩溃时)。

Linux具有类似的功能。但是,为每个操作系统实现特定于操作系统的代码是耗时且复杂的任务,因此使用一些便携式解决方案似乎是个好主意。

boost::process声称是这样的解决方案。但是,从根本上讲,这是不可靠的。请参阅以下示例程序:

#include <fstream>
#include <iostream>
#include <memory>
#include <vector>

#include <boost/asio.hpp>
#include <boost/process.hpp>

void ReadPipe(boost::process::async_pipe& pipe, char* output_buffer, size_t output_buffer_size, boost::asio::io_context::strand& executor, std::ofstream& output_saver)
{
    namespace io = boost::asio;
    using namespace std;
    io::async_read(
        pipe,
        io::buffer(
            output_buffer,
            output_buffer_size
        ),
        io::bind_executor(executor, [&pipe, output_buffer, output_buffer_size, &executor, &output_saver](const boost::system::error_code& error, std::size_t bytes_transferred) mutable
            {
                // Save transferred data
                if (bytes_transferred)
                    output_saver.write(output_buffer, bytes_transferred);
                // Handle error
                if (error)
                {
                    if (error.value() == boost::asio::error::basic_errors::broken_pipe)
                        cout << "Child standard output is broken, so the process is most probably exited." << endl;
                    else
                        cout << "Child standard output read error occurred. " << boost::system::system_error(error).what() << endl;
                }
                else
                {
                    //this_thread::sleep_for(chrono::milliseconds(50));
                    ReadPipe(pipe, output_buffer, output_buffer_size, executor, output_saver);
                }
            })
    );
}

int main(void)
{
    namespace io = boost::asio;
    namespace bp = boost::process;
    using namespace std;
    // Initialize
    io::io_context asio_context;
    io::io_context::strand executor(asio_context);
    bp::async_pipe process_out(asio_context);
    char buffer[65535];
    constexpr const size_t buffer_size = sizeof(buffer);
    ofstream output_saver(R"__(c:\screen.png)__", ios_base::out | ios_base::binary | ios_base::trunc);
    // Schedule to read standard output
    ReadPipe(process_out, buffer, buffer_size, executor, output_saver);
    // Run child
    bp::child process(
        bp::search_path("adb"),
        bp::args({ "exec-out", "screencap", "-p" }),
        bp::std_in.close(),
        bp::std_out > process_out,
        bp::std_err > process_out
    );
    asio_context.run();
    process.wait();
    output_saver.close();
    // Finish
    return 0;
}

这段代码很好用;它运行ADB,生成Android设备屏幕截图,并通过异步管道进行保存,因此不涉及任何临时文件。这个特定的示例将屏幕截图保存为文件,但是在实际应用中,您可以将数据保存在内存中,进行加载和解析。

我在示例中使用了ADB,因为该工具提供了很好的数据生成示例,这些数据生成速度相对较慢,并且通过USB或Wi-Fi发送(同样也很慢),并且数据大小相对较大(对于具有复杂图片,PNG文件将为1M +)。

当我取消注释以下行时:

this_thread::sleep_for(chrono::milliseconds(50));

管道读取操作变得完全不可靠。该程序仅读取部分数据(大小无法预测)。

因此,即使只有50毫秒的短暂延迟,也会迫使异步管道的实现失败。

这不是正常情况。如果CPU使用率接近100%(即我们在高负载的服务器上)怎么办?如果线程运行可能在50毫秒或更短时间内执行的其他ASIO作业怎么办?因此,它只是基本的Boost ASIO错误的易于复制的实现:异步管道在您开始阅读它时不能容忍任何延迟;您必须在收到数据后立即再次致电async_read,否则就有丢失数据的危险。

在实践中,当我使用相同的ASIO上下文运行多个作业(不仅有一个async_read读取过程标准输出)时,async_pipe在读取1M或更多数据的尝试中失败了50%

有人知道一种解决方法,如果ASIO上下文运行async_pipe时,运行其他作业所需的延迟很小,那么如何使async_read可靠并且不中断连接?

0 个答案:

没有答案