1

该属性可以这样描述:如果该操作被取消,则它的处理程序保证在执行时出现错误

例如,boost::asio::deadline_timer没有该属性,如deadline_timer::cancel function 文档的备注部分所述。因此,即使取消计时器上的等待操作,它的回调也有可能在没有错误的情况下执行。

另一方面,该属性适用于 asio 套接字(至少我希望如此 :),因为文档中没有这样的评论)。

编辑:一个伪代码演示了截止时间计时器中缺少此属性:

1# User calls timer.async_wait with a handler H which is to be
   executed when the action finishes.
2# Time passes.
3# Timeout has been reached, asio internally inserts the handler H into
   a queue for later execution, but with error code indicating success.
   User is unaware of when this step takes place.
4# User calls cancel on the timer, thus would expect the handler to be
   executed with an error code indicating failure.
5# Asio takes the handler H from the queue and executes it with error
   code indicating success as set in the step #3.

只需在步骤 #4 中设置一个布尔标志,然后在步骤 #5 中检查它,就可以轻松解决此问题,所以这不是问题。

4

1 回答 1

4

TR2 的文档网络库提案都没有定义异步操作的术语,在取消时保证错误。但是,所有异步操作,包括boost::asio::deadline_timer::async_wait(),都会表现出这种行为。处理程序的设计使其始终提供相关操作的状态。如果没有这种保证,当发生取消或多个操作时,开发人员将在处理程序中处于未知状态。

取消仅适用于尚未发生的操作。我相信文档仅针对计时器类强调了这一点,因为异步操作的可见性不同。对 I/O 对象的操作具有很高的可见性。例如,可以嗅探网络以观察套接字上的异步写入操作。另一方面,定时器的操作可见性低。等待操作的机制是 Boost.Asio 中的一个实现细节,API 不提供对操作执行外部监控的能力。

在伪代码中,已经到了超时,说明异步等待操作已经完成。因此,它不能再被取消,因为该操作已经发生。因此,处理程序将不会被错误调用boost::asio::error::operation_aborted。重要的是要理解取消是一种操作,而不是状态变化。因此,无法查询计时器以查看是否发生了取消。此外,如果用户希望取消修改处理程序的错误代码,那么用户可能会迷失在异步操作的启动、完成和通知之间的时间分离导致的继承复杂性中。


以下所有内容都非常具体地介绍了实现细节。在这种情况下,在 Boost.Asio 将使用 epoll 作为其反应器的系统上,使用boost::asio::ip::tcp::socket::async_receive在套接字上进行异步读取。

初始化

  1. 当创建套接字对象时,它的服务会reactive_socket_service_base通知反应器初始化任务(reactor::init_task())。
  2. 然后反应器将通知io_service初始化任务。这会导致将标记操作添加到io_service的操作队列中。
  3. 当套接字以有效的文件描述符打开时,与文件描述符 ( descriptor_data) 关联的数据结构将注册到反应器。这个结构体有自己的操作队列,实际上它本身就是一个操作。
  4. 反应器从中提取文件描述符descriptor_data并将其添加到文件描述符列表中以在反应器内观察。

启动异步读操作

  1. boost::asio::detail::reactive_socket_service_base::async_receive()创建一个boost::asio::detail::reactive_socket_recv_op对象。这个对象是一个操作对象。它的perform()成员函数将尝试从套接字读取数据,并且它的complete()成员函数将调用用户的完成处理程序。
  2. boost::asio::detail::reactive_socket_service_base::start_op()使用读取操作调用,该操作调用reactor::start_op().
  3. 反应器descriptor_data为套接字获取,锁定特定于描述符的互斥体,将操作推入特定于描述符的操作队列,通知io_service有工作,然后解锁互斥体。

socket获取数据,异步读操作完成开始

  1. io_service::run*()被调用。
  2. 如果有任何操作准备好运行,io_service则只会检查其操作队列。在这种情况下,初始化期间创建的标记操作在队列中。该操作被标识为标记,表示存在反应器,并调用reactor::run().
  3. 反应堆将阻塞epoll_wait。当套接字的文件描述符有活动时,则标识该事件。从事件descriptor_data中提取,并将其推送到调用者的操作队列中,就像descriptor_data操作一样。
  4. 然后将传递回调用者的操作添加到io_service的操作队列中,该队列现在包含descriptor_data操作和原始标记操作。
  5. descriptor_data操作从io_service队列中弹出,并调用complete()成员函数,导致epoll_reactor::descriptor_state::do_complete运行。
  6. do_complete调用epoll_reactor::descriptor_state::perform_io,其中在descriptor_data操作队列中的操作被迭代并被perform()调用。这包括boost::asio::detail::reactive_socket_recv_op在异步操作启动期间被推入队列的操作。
  7. 读取操作的perform()成员函数将调用socket_ops::non_blocking_recv(),尝试从套接字读取实际数据。错误代码和传输的字节存储在操作中。
  8. 该操作从descriptor_data的操作队列中移除,并添加到io_servicevia task_io_service::post_deferred_completion

异步读取操作的通知

  1. 现在io_service它的队列中有两个操作:完成的读取操作和标记操作。其reactor队列中没有操作。
  2. 读操作最终被迭代到,并complete()调用其成员函数。在reactive_socket_recv_op::do_complete中,用户处理程序使用错误代码和传输的字节进行调用。

消除

  1. boost::asio::detail::reactive_socket_service_base::cancel()调用. reactor::cancel_ops_descriptor_data
  2. epoll_reactor::cancel_ops()遍历descriptor_data的操作队列。每个操作都从 的操作队列中弹出,将descriptor_data其错误代码设置为boost::asio::error::operation_aborted,然后添加到io_service的操作队列中。

因此,取消只会影响尚未通过从descriptor_data' 的操作队列中删除它们来调用的操作。如果一个操作已被调用,那么它已经从descriptor_data的操作队列中移除并添加到io_service操作队列中。

于 2013-01-02T01:57:30.883 回答