使用Boost.Asio链接异步Lambda表达式?

17

我发现自己写的代码基本上看起来像这样:

using boost::system::error_code;

socket.async_connect(endpoint, [&](error_code Error)
{
  if (Error)
  {
    print_error(Error);
    return;
  }

  // Read header
  socket.async_read(socket, somebuffer, [&](error_code Error, std::size_t N)
  {
    if (Error)
    {
      print_error(Error);
      return;
    }

    // Read actual data
    socket.async_read(socket, somebuffer, [&](error_code Error, std::size_t N)
    {
      // Same here...
    });
  });
};

基本上,我正在嵌套回调函数中的回调函数,而逻辑是简单和“线性”的。

是否有更优雅的编写方式,使代码既本地又有序?


此外,这变得非常奇怪,因为VC++10似乎不允许捕获在周围lambda中已经捕获的变量。 - ltjax
我认为答案是否定的。你不能使用链式lambda表达式而不将它们嵌入到彼此中。 - Galimov Albert
@PSIAlt:我在想像continuations这样的东西(见http://msdn.microsoft.com/en-us/library/dd492427.aspx#continuations)。只是不确定它是否能与asio很好地配合。 - ltjax
@ltjax:Continuations提供了优雅的解决方案吗?我尝试着去适应一个解决方案,但它既不简单也不优雅。一旦我尝试执行任何超出停止整个continuation的分支,问题就会加剧。 - Tanner Sansbury
1个回答

16

一个优雅的解决方案是使用协程。Boost.Asio支持stackless协程,它们引入了一小组伪关键字,以及stackful协程,它们使用Boost.Coroutine


无栈协程

无栈协程引入了一组伪关键字预处理宏,使用类似于达夫设备的技术实现了一个switch语句。文档详细介绍了每个关键字。

使用无栈协程实现原始问题(连接->读取头文件->读取正文)可能如下所示:

struct session
 : boost::asio::coroutine
{
  boost::asio::ip::tcp::socket socket_;
  std::vector<char> buffer_;
  // ...

  void operator()(boost::system::error_code ec = boost::system::error_code(),
                  std::size_t length = 0)  
  {
    // In this example we keep the error handling code in one place by
    // hoisting it outside the coroutine. An alternative approach would be to
    // check the value of ec after each yield for an asynchronous operation.
    if (ec)
    {
      print_error(ec);
      return;
    }

    // On reentering a coroutine, control jumps to the location of the last
    // yield or fork. The argument to the "reenter" pseudo-keyword can be a
    // pointer or reference to an object of type coroutine.
    reenter (this)
    {
      // Asynchronously connect. When control resumes at the following line,
      // the error and length parameters reflect the result of
      // the asynchronous operation.
      yield socket_.async_connect(endpoint_, *this);

      // Loop until an error or shutdown occurs.
      while (!shutdown_)
      {
        // Read header data. When control resumes at the following line,
        // the error and length parameters reflect the result of
        // the asynchronous operation.
        buffer_.resize(fixed_header_size);
        yield socket_.async_read(boost::asio::buffer(buffer_), *this);

        // Received data.  Extract the size of the body from the header.
        std::size_t body_size = parse_header(buffer_, length);

        // If there is no body size, then leave coroutine, as an invalid
        // header was received.
        if (!body_size) return;

        // Read body data. When control resumes at the following line,
        // the error and length parameters reflect the result of
        // the asynchronous operation.
        buffer_.resize(body_size);
        yield socket_.async_read(boost::asio::buffer(buffer_), *this);

        // Invoke the user callback to handle the body.
        body_handler_(buffer_, length);
      }

      // Initiate graceful connection closure.
      socket_.shutdown(tcp::socket::shutdown_both, ec);
    } // end reenter
  }
}

堆栈式协程

使用spawn()函数创建堆栈式协程。当使用堆栈式协程实现时,原始问题可能如下所示:

boost::asio::spawn(io_service, [&](boost::asio::yield_context yield)
  {
    boost::system::error_code ec;
    boost::asio::ip::tcp::socket socket(io_service);

    // Asynchronously connect and suspend the coroutine.  The coroutine will
    // be resumed automatically when the operation completes.
    socket.async_connect(endpoint, yield[ec]);
    if (ec)
    {
      print_error(ec);
      return;
    }

    // Loop until an error or shutdown occurs.
    std::vector<char> buffer;
    while (!shutdown)
    {
      // Read header data.
      buffer.resize(fixed_header_size);
      std::size_t bytes_transferred = socket.async_read(
        boost::asio::buffer(buffer), yield[ec]);

      if (ec)
      {
        print_error(ec);
        return;
      }

      // Extract the size of the body from the header.
      std::size_t body_size = parse_header(buffer, bytes_transferred);

      // If there is no body size, then leave coroutine, as an invalid header
      // was received.
      if (!body_size) return;

      // Read body data.
      buffer.resize(body_size);
      bytes_transferred =
        socket.async_read(boost::asio::buffer(buffer), yield[ec]);

      if (ec)
      {
        print_error(ec);
        return;
      }

      // Invoke the user callback to handle the body.
      body_handler_(buffer, length);
    }

    // Initiate graceful connection closure.
    socket.shutdown(tcp::socket::shutdown_both, ec);
   });

谢谢您的建议,我会看一下的! - ltjax
Boost 1_54+ 满载着对 boost::asio::spawn 的栈式协程的支持:http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/overview/core/spawn.html - sehe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接