Complete Token

Boost.Asio 异步模型的一个关键目标是支持多种组合机制。这是通过一个完成令牌(Completion Token)来实现的,用户将其传递给异步操作的启动函数,以自定义库的 API 表面。按照惯例,完成令牌是异步操作启动函数的最后一个参数。
例如,如果用户传递一个 lambda(或其他函数对象)作为完成令牌,异步操作将表现如前述:操作开始后,当操作完成时,结果将传递给该 lambda。
socket.async_read_some(buffer,
[](error_code e, size_t)
{
// ...
}
);当用户传递 use_future 完成令牌时,操作的行为就像是通过 promise 和 future 对来实现的。启动函数不仅会启动操作,还会返回一个 future,用户可以使用它来等待结果。
同样地,当用户传递 use_awaitable 完成令牌时,启动函数的行为就像是基于 awaitable 的协程(Boost.Asio库中包含了awaitable类模板,作为C++20协程的返回类型。 这些协程可以通过其他基于 awaitable 的协程来实现。)。然而,在这种情况下,启动函数并不会直接启动异步操作。它仅返回一个 awaitable,异步操作会在被 co_await 时启动。
最后,yield_context 完成令牌使启动函数在有堆栈协程的上下文中表现为同步操作。它不仅启动异步操作,还会阻塞有堆栈协程,直到操作完成。从有堆栈协程的角度来看,这就是一个同步操作。
所有这些用法都由单一的 async_read_some 启动函数实现支持。
为实现这一点,异步操作必须首先指定一个完成签名(或可能多个签名),该签名描述了将传递给其完成处理程序的参数。
接下来,操作的启动函数将完成签名、完成令牌及其内部实现传递给 async_result 特性。async_result 特性是一个自定义点,它将这些元素组合起来,首先生成一个具体的完成处理程序,然后启动操作。

为了在实践中了解这一点,让我们使用分离线程将同步操作调整为异步操作(仅供参考。 不建议实际使用!):
注意下边的条目与上边代码的 *数字 是对应的
completion_token_for概念用于检查用户提供的完成令牌是否符合指定的完成签名。对于较旧版本的 C++,可以简单地使用typename。定义一个函数对象,其中包含启动异步操作的代码。该对象会接收具体的完成处理程序,以及通过
async_result特性传递的任何其他参数。函数对象的主体部分会生成一个新线程来执行该操作。
一旦操作完成,结果将传递给完成处理程序。
async_result特性接收(衰变后的 decayed)完成令牌类型和异步操作的完成签名。调用特性的
initiate成员函数,首先传递启动操作的函数对象。接下来传递转发的完成令牌。特性实现会将其转换为一个具体的完成处理程序。
最后,传递函数对象的任何其他参数。假设这些参数可以被特性实现衰变复制或移动。
在实际操作中,我们应该调用 async_initiate 辅助函数,而不是直接使用 async_result 特性。async_initiate 函数会自动执行完成令牌的必要衰变和转发,并且还能向后兼容旧的完成令牌实现。
我们可以将完成令牌视为一种原型完成处理程序。在我们传递一个函数对象(如 lambda)作为完成令牌的情况下,它已经满足了完成处理程序的要求。async_result 主模板通过简单地转发参数来处理这种情况:
我们可以看到,默认实现避免了对所有参数的拷贝,从而确保了尽早启动操作时的高效性。
另一方面,惰性完成令牌(例如上面的 use_awaitable)可能会捕获这些参数以延迟启动操作。例如,一个用于简单延迟令牌(仅将操作打包以便稍后执行)的特化可能如下所示:
Last updated