Boost C++ 库

...是世界上备受推崇且设计精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ Coding Standards

完成令牌 - Boost C++ 函数库
PrevUpHomeNext

Boost.Asio 的异步模型的一个关键目标是支持多种组合机制。这通过一个 完成令牌 来实现,用户将其传递给异步操作的启动函数,以自定义库的 API 表面。根据约定,完成令牌是异步操作启动函数的最后一个参数。

例如,如果用户将 lambda(或其他函数对象)作为完成令牌传递,异步操作将如前所述运行:操作开始,当操作完成时,结果将传递给 lambda。

socket.async_read_some(buffer,
    [](error_code e, size_t)
    {
      // ...
    }
  );

当用户传递 use_future 完成令牌时,操作将如同使用 promisefuture 对来实现一样运行。启动函数不仅启动操作,还会返回一个 future,可用于等待结果。

future<size_t> f =
  socket.async_read_some(
      buffer, use_future
    );
// ...
size_t n = f.get();

同样,当用户传递 use_awaitable 完成令牌时,启动函数将如同一个基于 awaitable 的协程 [6]。但是,在这种情况下,启动函数不会直接启动异步操作。它只返回 awaitable,当它被 co_await 时,后者才会启动操作。

awaitable<void> foo()
{
  size_t n =
    co_await socket.async_read_some(
        buffer, use_awaitable
      );

  // ...
}

最后,yield_context 完成令牌会导致启动函数在堆栈式协程的上下文中表现为同步操作。它不仅开始异步操作,还会阻塞堆栈式协程直到操作完成。从堆栈式协程的角度来看,这是一个同步操作。

void foo(boost::asio::yield_context yield)
{
  size_t n = socket.async_read_some(buffer, yield);

  // ...
}

所有这些用法都由 async_read_some 启动函数的单个实现支持。

为此,异步操作必须首先指定一个 完成签名(或可能多个签名),该签名描述将传递给其完成处理程序的参数。

然后,操作的启动函数会获取完成签名、完成令牌及其内部实现,并将它们传递给 async_result 特性。 async_result 特性是一个自定义点,它将这些组合在一起,首先生成一个具体的完成处理程序,然后启动操作。

为了在实践中看到这一点,让我们使用一个独立的线程将同步操作适配成异步操作:[7]

template <
    completion_token_for<void(error_code, size_t)> 1
      CompletionToken>
auto async_read_some(
    tcp::socket& s,
    const mutable_buffer& b,
    CompletionToken&& token)
{
  auto init = []( 2
      auto completion_handler,
      tcp::socket* s,
      const mutable_buffer& b)
  {
    std::thread( 3
        [](
            auto completion_handler,
            tcp::socket* s,
            const mutable_buffer& b
          )
        {
          error_code ec;
          size_t n = s->read_some(b, ec);
          std::move(completion_handler)(ec, n); 4
        },
        std::move(completion_handler),
        s,
        b
      ).detach();
  };

  return async_result< 5
      decay_t<CompletionToken>,
      void(error_code, size_t)
    >::initiate(
        init, 6
        std::forward<CompletionToken>(token), 7
        &s, 8
        b
      );
}

1

completion_token_for 概念检查用户提供的完成令牌是否满足指定的完成签名。对于旧版本的 C++,只需在此处使用 typename 即可。

2

定义一个包含启动异步操作代码的函数对象。它会接收具体的完成处理程序,然后是传递给 async_result 特性的任何其他参数。

3

函数对象的正文会生成一个新线程来执行操作。

4

操作完成后,会将结果传递给完成处理程序。

5

async_result 特性接收(衰减后的)完成令牌类型,以及异步操作的完成签名。

6

调用特性的 initiate 成员函数,首先传递启动操作的函数对象。

7

接下来是转发的完成令牌。特性实现会将此转换为具体的完成处理程序。

8

最后,传递函数对象的任何其他参数。假设这些参数可以被特性实现衰减复制和移动。

实际上,我们应该调用 async_initiate 辅助函数,而不是直接使用 async_result 特性。 async_initiate 函数会自动执行必要的衰减和转发完成令牌,并支持向后兼容旧的完成令牌实现。

template <
    completion_token_for<void(error_code, size_t)>
      CompletionToken>
auto async_read_some(
    tcp::socket& s,
    const mutable_buffer& b,
    CompletionToken&& token)
{
  auto init = /* ... as above ... */;

  return async_initiate<
      CompletionToken,
      void(error_code, size_t)
    >(init, token, &s, b);
}

我们可以将完成令牌视为一种 proto 完成处理程序。当我们把函数对象(如 lambda)作为完成令牌传递时,它已经满足了完成处理程序的要求。async_result 主模板通过简单地转发参数来处理这种情况

template <class CompletionToken, completion_signature... Signatures>
struct async_result
{
  template <
      class Initiation,
      completion_handler_for<Signatures...> CompletionHandler,
      class... Args>
  static void initiate(
      Initiation&& initiation,
      CompletionHandler&& completion_handler,
      Args&&... args)
  {
    std::forward<Initiation>(initiation)(
        std::forward<CompletionHandler>(completion_handler),
        std::forward<Args>(args)...);
  }
};

我们可以在这里看到,这个默认实现避免了所有参数的拷贝,从而确保了积极启动尽可能高效。

另一方面,懒惰的完成令牌(如上面的 use_awaitable)可能会捕获这些参数以延迟启动操作。例如,一个简单的 deferred 令牌(仅打包操作以备后用)的特化可能看起来像这样

template <completion_signature... Signatures>
struct async_result<deferred_t, Signatures...>
{
  template <class Initiation, class... Args>
  static auto initiate(Initiation initiation, deferred_t, Args... args)
  {
    return [
        initiation = std::move(initiation),
        arg_pack = std::make_tuple(std::move(args)...)
      ](auto&& token) mutable
    {
      return std::apply(
          [&](auto&&... args)
          {
            return async_result<decay_t<decltype(token)>, Signatures...>::initiate(
                std::move(initiation),
                std::forward<decltype(token)>(token),
                std::forward<decltype(args)>(args)...
              );
          },
          std::move(arg_pack)
        );
    };
  }
};


[6] awaitable 类模板包含在 Boost.Asio 库中,作为 C++20 协程的返回类型。这些协程可以很容易地用其他基于 awaitable 的协程来实现。

[7] 仅用于说明目的。不推荐在实际中使用!


PrevUpHomeNext