When do handlers for cancelled boost::asio handler

2019-04-02 06:11发布

问题:

The boost docs say that cancelled async connect, send and receive finish immediately, and the handlers for cancelled operations will be passed the boost::asio::error::operation_aborted error.

I would like to find out if the cancelled handler gets to run (and see the operation_aborted error) before other (non-cancelled, and newly scheduled) completion handlers run.

Here is the timeline that concerns me:

acceptHandler and readHandler are running on the same event loop and the same thread.

  • time t0 - readHandler is running on oldConnectionSocket
  • time t1 - acceptHandler runs
  • time t2 - acceptHandler calls oldConnectionSocket.cancel
  • time t3 - acceptHandler closes oldConnectionSocket
  • time t4 - acceptHandler calls newConnectionSocket.async_read(...readHandler...)
  • time t5 - readHandler is called (from which context?)

Is it possible at t5 for readHandler to be called in the newConnectionSocket context before it is called with the operation_aborted error in the oldConnectionSocket context?

回答1:

Cancelled operations will immediately post their handlers for deferred invocation. However, the io_service makes no guarantees on the invocation order of handlers. Thus, the io_service could choose to invoke the ReadHandlers in either order. Currently, only a strand specifies guaranteed ordering under certain conditions.

Within a completion handler, if the goal is to know which I/O object was associated with the operation, then consider constructing the completion handler so that it has an explicit handle to the I/O object. This is often accomplished using any of the following:

  • a custom functor
  • std::bind() or boost::bind()
  • a C++11 lambda

One common idiom is to have the I/O object be managed by a single class that inherits from boost::enable_shared_from_this<>. When a class inherits from boost::enable_shared_from_this, it provides a shared_from_this() member function that returns a valid shared_ptr instance to this. A copy of the shared_ptr is passed to completion handlers, such as a capture-list in lambdas or passed as the instance handle to boost::bind(). This allows for the handlers to know the I/O object on which the operation was performed, and causes the lifetime of the I/O object to be extended to at least as long as the handler. See the Boost.Asio asynchronous TCP daytime server tutorial for an example using this approach.

class tcp_connection
  : public boost::enable_shared_from_this<tcp_connection>
{
public:

  // ...

  void start()
  {    
    boost::asio::async_write(socket_, ...,
        boost::bind(&tcp_connection::handle_write, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_write(
    const boost::system::error_code& error,
    std::size_t bytes_transferred)
  {
    // I/O object is this->socket_.
  }

  tcp::socket socket_;
};

On the other hand, if the goal is to determine if one handler has executed before the other, then:

  • the application will need to explicitly manage the state
  • trying to manage multiple dependent call chains may be introducing unnecessary complexity and often indicates a need to reexamine the design
  • custom handlers could be used to prioritize the order in which handlers are executed. The Boost.Asio Invocation example uses custom handlers that are added to a priority queue, which are then executed at a later point in time.