传递给 boost::asio::async_read_some 的回调从未在 boost::asio::read_some 返回数据的使用中调用

Posted

技术标签:

【中文标题】传递给 boost::asio::async_read_some 的回调从未在 boost::asio::read_some 返回数据的使用中调用【英文标题】:Callback passed to boost::asio::async_read_some never invoked in usage where boost::asio::read_some returns data 【发布时间】:2014-11-22 03:25:58 【问题描述】:

我一直致力于通过学习使用 boost::asio::basic_serial_port 的基本串行终端示例来实现半双工串行驱动程序: http://lists.boost.org/boost-users/att-41140/minicom.cpp

我需要异步读取,但仍会检测到处理程序何时在主线程中完成,因此我使用 boost:bind 在 lambda 函数中传递了一个带有多个附加引用参数的 async_read_some 回调。处理程序永远不会被调用,但如果我用 read_some 函数替换 async_read_some 函数,它会毫无问题地返回数据。

我相信我满足了该函数调用处理程序的所有必要要求,因为它们对于 asio::read 一些返回的函数是相同的:

    缓冲区保持在范围内 串行设备接收到一个或多个字节 io 服务正在运行 端口已打开并以正确的波特率运行

有谁知道我是否遗漏了异步读取特有的另一个假设,或者我没有正确设置 io_service?

以下是我如何将代码与 async_read_some (http://www.boost.org/doc/libs/1_56_0/doc/html/boost_asio/reference/basic_serial_port/async_read_some.html) 一起使用的示例:

void readCallback(const boost::system::error_code& error, size_t bytes_transfered, bool & finished_reading, boost::system::error_code& error_report, size_t & bytes_read)

    std::cout << "READ CALLBACK\n";
    std::cout.flush();
    error_report = error;
    bytes_read = bytes_transfered;
    finished_reading = true;
    return;


int main()

    int baud_rate = 115200;
    std::string port_name = "/dev/ttyUSB0";
    boost::asio::io_service io_service_;
    boost::asio::serial_port serial_port_(io_service_,port_name);
    serial_port_.set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
    boost::thread service_thread_;
    service_thread = boost::thread(boost::bind(&boost::asio::io_service::run, &io_service_));

    std::cout << "Starting byte read\n";
    boost::system::error_code ec;

    bool finished_reading = false;
    size_t bytes_read;
    int max_response_size = 8;
    uint8_t read_buffer[max_response_size];
    serial_port_.async_read_some(boost::asio::buffer(read_buffer, max_response_size),
                     boost::bind(readCallback,
                                 boost::asio::placeholders::error,
                                 boost::asio::placeholders::bytes_transferred,
                                 finished_reading, ec, bytes_read));

    std::cout << "Waiting for read to finish\n";
    while (!finished_reading)
    
        boost::this_thread::sleep(boost::posix_time::milliseconds(1));
    

    std::cout << "Finished byte read: " << bytes_read << "\n";

    for (int i = 0; i < bytes_read; ++i)
    
    printf("0x%x ",read_buffer[i]);
    

结果是回调没有打印出任何内容,并且 while !finished 循环永远不会结束。

这是我使用阻塞 read_some 函数的方法(boost.org/doc/libs/1_56_0/doc/html/boost_asio/reference/basic_serial_port/read_some.html):

int main()

    int baud_rate = 115200;
    std::string port_name = "/dev/ttyUSB0";
    boost::asio::io_service io_service_;
    boost::asio::serial_port serial_port_(io_service_,port_name);
    serial_port_.set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
    boost::thread service_thread_;
    service_thread = boost::thread(boost::bind(&boost::asio::io_service::run, &io_service_));

    std::cout << "Starting byte read\n";
    boost::system::error_code ec;
    int max_response_size = 8;
    uint8_t read_buffer[max_response_size];
    int bytes_read = serial_port_.read_some(boost::asio::buffer(read_buffer, max_response_size),ec);

    std::cout << "Finished byte read: " << bytes_read << "\n";

    for (int i = 0; i < bytes_read; ++i)
    
    printf("0x%x ",read_buffer[i]);
    

此版本打印我发送的 1 到 8 个字符,阻塞直到至少发送一个。

【问题讨论】:

【参考方案1】:

代码不保证io_service 正在运行。 io_service::run() 将在以下任一情况下返回:

所有工作都已完成,没有更多的处理程序要分派 io_service 已停止。

在这种情况下,可以在启动 serial_port::async_read_some() 操作之前创建 service_thread_ 并调用 io_service::run(),向 io_service 添加工作。因此,service_thread_ 可以立即从io_service::run() 返回。要解决此问题,可以:

在异步操作启动后调用io_service::run()。 在启动service_thread_ 之前创建一个io_service::work 对象。 work 对象可防止 io_service 耗尽工作。

此answer 可能会提供对io_service::run() 行为的更多见解。


其他一些需要注意和扩展的事情 Igor's answer:

如果线程在等待异步操作完成时没有以有意义的方式进行(即在循环中旋转睡眠),那么可能值得研究将同步行为与异步操作混合是否是正确的解决方案。李>

boost::bind() 按值复制其参数。要通过引用传递参数,请将其包装为 boost::ref()boost::cref()

boost::bind(..., boost::ref(finished_reading), boost::ref(ec),
            boost::ref(bytes_read));

需要添加同步以保证主线程中finished_reading 的内存可见性。对于异步操作,Boost.Asio 将guarantee 适当的内存屏障以确保正确的内存可见性(有关更多详细信息,请参阅此answer)。在这种情况下,主线程中需要一个memory barrier,以保证主线程观察到其他线程对finished_reading 的更改。考虑使用 Boost.Thread 同步机制,如 boost::mutex,或 Boost.Atomic 的 atomic objects 或 thread and signal fences。

【讨论】:

感谢您的回答,它解决了我示例中的所有问题。您对 io_service::run 行为的回答的链接非常具有说明性。这只是一个测试代码,我的实际代码根据旋转循环中主线程范围内的数据包解析细节实现超时。如果数据来自半双工总线并且本质上是同步的(只要没有出错),使用异步读取的同步结构是否合理,这样我可以确保超时并清理混乱读取失败? 我想我理解你所说的重新检查同步和异步行为的使用。似乎我应该链接异步调用以填充缓冲区,然后在发送请求后在同步线程中读取它。 @st3am 如果需要超时,则需要异步操作。但是,为此使用额外的线程可能会使解决方案复杂化。考虑检查官方 Boost.Asio timeout examples 中使用的超时方法。对于半双工系统,通常可以使用单个线程等待读取完成或超时,然后响应并重复。 对于其他尝试从 linux 上的半双工设备读取的人:我发现半双工架构在我正在读取的 ftdi 芯片处结束,至少就我而言,我不必这样做担心在我写作时避免阅读。使用 Tanners 的所有建议,我能够阅读,但如果读取和写入之间的时间变长,字节偶尔会丢失。我认为这通常发生在上下文切换中,如果我在写入和读取之间引入几毫秒的延迟,所有字节都会丢失。使用 async_read_some 调用链来填充 std::deque 是解决方案。【参考方案2】:

请注意boost::bind copies its arguments。如果你想通过引用传递一个参数,用boost::ref(或std::ref)包装它:

boost::bind(readCallback, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, boost::ref(finished_reading), ec, bytes_read));

(但是,严格来说,您传递给另一个线程的bool 变量存在竞争条件。更好的解决方案是使用std::atomic_bool。)

【讨论】:

感谢您的回答,使用 ref 包装器允许在运行事件尚未返回时修改回调参数。

以上是关于传递给 boost::asio::async_read_some 的回调从未在 boost::asio::read_some 返回数据的使用中调用的主要内容,如果未能解决你的问题,请参考以下文章

无法正确地将值从父母传递给孩子和孩子传递给父母

如何将对象数组作为道具传递给组件,然后将数组的成员作为道具传递给嵌套组件?

为啥将 ClientID 传递给 javascript 函数会传递整个控件?

将变量传递给 jQuery 插件参数

如何将环境变量传递给传递给xmllint的命令?

将 JSON 数据从 php 传递给 html-data 属性,然后传递给 Javascript